API监听和WebSocket监听移至注入的js脚本

This commit is contained in:
zhiyong.yue 2023-11-01 18:44:03 +08:00
parent e73b03b4f2
commit 6aecdb17d6
4 changed files with 58 additions and 110 deletions

View File

@ -1,10 +1,8 @@
import { test as base, TestInfo } from "@playwright/test";
import { test as base, expect, TestInfo } from "@playwright/test";
import type {
Browser,
BrowserContext,
Page,
Response,
WebSocket,
} from "playwright-core";
import PagesInstance from "./PagesInstance";
@ -23,7 +21,7 @@ async function newContext(
context: BrowserContext;
close: (testInfo: TestInfo) => Promise<void>;
}> {
const pages = [];
let pages: Page[] = [];
const video = testInfo.project.use.video;
const videoMode = normalizeVideoMode(video);
const captureVideo = shouldCaptureVideo(videoMode, testInfo);
@ -36,10 +34,12 @@ async function newContext(
}
: {};
const context = await browser.newContext(videoOptions);
await test.step("DeleteFromTheHtmlreport-监听", async () => {
context.on("response", (data) => ListenResponse(data));
context.on("page", (page) => onPage(pages, page));
});
context.on("dialog", async (dialog) => {
const message = dialog.message();
await dialog.accept();
expect.soft(message, { message: "API报错" + message }).not.toContain("failInfo")
})
context.on("page", (page) => pages.push(page));
// 使用MutationObserver监听DOM变化结合PerformanceObserver获取最后一次响应的返回时间以达到loading时禁止点击输入等操作.
await context.addInitScript({
@ -111,75 +111,6 @@ function shouldCaptureVideo(videoMode: string, testInfo: TestInfo): boolean {
);
}
interface WSMessage {
message: {
subject?: string;
content?: string;
};
}
async function ListenWebScoket(page: Page, ws: WebSocket): Promise<void> {
const wsmsg = await new Promise((resolve) => {
ws.on("framereceived", (event) => {
console.log(event.payload);
resolve(event.payload);
});
});
// Extract subject and content from wsmsg if it contains message with subject or content
let subject = "",
content = "";
if (wsmsg && typeof wsmsg === "object" && (wsmsg as WSMessage).message) {
const message = (wsmsg as WSMessage).message;
subject = message.subject || "";
content = message.content || "";
// remove html tag
content = content.replace(/<\/?[^>]+(>|$)/g, "");
}
let popup = page
.locator(
".c7n-notification-notice:has(.c7n-notification-notice-icon:not(.icon))"
)
.locator("visible=true");
if (subject) {
popup = popup.filter({ hasText: subject });
}
if (content) {
for (const char of content) {
popup = popup.filter({ hasText: char });
}
}
await popup.evaluate((node) => (node.style.display = "none"));
}
async function ListenResponse(response: Response): Promise<void> {
await test.step("DeleteFromTheHtmlreport-监听", async () => {
if (!response.url().includes("https://cdn-")) {
if (response.status() === 403) {
console.log("Response status code is 403");
} else if (
response.status() === 200 &&
(await response.headerValue("Content-Type")) === "application/json"
) {
try {
const resJson = await response.json();
if (resJson.failed) {
console.log(resJson.message);
}
} catch (e) {
// Silent catch empty videos.
}
}
}
});
}
async function onPage(pages: Array<Page>, page: Page) {
pages.push(page);
await test.step("DeleteFromTheHtmlreport-监听", async () => {
page.on("websocket", (ws) => ListenWebScoket(page, ws));
});
}
type Accounts = {
user_1: PagesInstance;
user_2: PagesInstance;

View File

@ -129,7 +129,7 @@ var ajaxHooker = (function () {
emptyFn,
errorFn
);
} catch {
} catch (err) {
console.error(err);
return Promise.resolve();
}

View File

@ -28,7 +28,7 @@
});
ajaxHooker.filter([
{
url: /^https:\/\/(test|demo)\.cloudlong.cn/,
url: /^https:\/\/(oc-test|demo)\.cloudlong.cn/,
async: true,
},
]);
@ -42,6 +42,12 @@
document.body.appendChild(mask);
}
request.response = (res) => {
if (res.status === 403){
win.alert("API:" + res.finalUrl + "-----failInfo:Response status is 403");
}else if(res.json && res.json.hasOwnProperty("failed"))
{
win.alert("API:" + res.finalUrl + "-----failInfo:" + res.json.message);
}
if (
win.apiCounter === 0 &&
document.getElementById("networkIdleMask")

View File

@ -1,5 +1,9 @@
// 观察DOM变化对新增的按钮等进行disable或hidden,同时获取loading元素数量
const domObserver = new MutationObserver((mutationsList) => {
// listenElementsClassName中元素出现style.display属性将被设置为none,且不会被恢复,防止此类元素出现影响UI自动化操作其他元素
// 如需判断页面中存在这些这些元素,只需playwrigh locator.waitFor("attached")即可.
const listenElementsClassName = ["c7n-notification-notice request"];
// 临时被禁用,网络结束后启用的元素
const findAllElementsNeedToDisable = (element) => [
...(element.tagName === "BUTTON" && !element.disabled
? [(element.disabled = true && element)]
@ -17,15 +21,22 @@ const domObserver = new MutationObserver((mutationsList) => {
let elementsToRestore = [];
for (const mutation of mutationsList) {
if (mutation.type === "childList") {
if (mutation.target instanceof HTMLElement && mutation.target.classList) {
if (mutation.addedNodes.length > 0) {
const disabledElements = Array.from(
mutation.addedNodes || []
).flatMap(findAllElementsNeedToDisable);
const { target, addedNodes } = mutation;
if (target instanceof HTMLElement && target.classList && addedNodes.length > 0) {
const disabledElements = Array.from(addedNodes || []).flatMap(findAllElementsNeedToDisable);
elementsToRestore = elementsToRestore.concat(disabledElements);
addedNodes.forEach((addedNode) => {
if (typeof addedNode.className === 'string') {
for (const className of listenElementsClassName) {
if (addedNode.className.includes(className)) {
addedNode.style.display = "none";
break;
}
}
}
});
}
}
}
// 当目前无loading,且最后一次网络请求结束timeOut时间以上,恢复元素状态.
if (elementsToRestore.length > 0) {