From 6aecdb17d6bc2388da47fc7d2205a98157d0fc56 Mon Sep 17 00:00:00 2001 From: "zhiyong.yue" Date: Wed, 1 Nov 2023 18:44:03 +0800 Subject: [PATCH] =?UTF-8?q?API=E7=9B=91=E5=90=AC=E5=92=8CWebSocket?= =?UTF-8?q?=E7=9B=91=E5=90=AC=E7=A7=BB=E8=87=B3=E6=B3=A8=E5=85=A5=E7=9A=84?= =?UTF-8?q?js=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/BaseTest.ts | 85 +++++--------------------------------------- tools/ajaxHooker.js | 2 +- tools/apiObserver.js | 50 ++++++++++++++------------ tools/domObserver.js | 31 ++++++++++------ 4 files changed, 58 insertions(+), 110 deletions(-) diff --git a/tests/BaseTest.ts b/tests/BaseTest.ts index a02f969..5e54771 100644 --- a/tests/BaseTest.ts +++ b/tests/BaseTest.ts @@ -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; }> { - 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 { - 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 { - 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) { - pages.push(page); - await test.step("DeleteFromTheHtmlreport-监听", async () => { - page.on("websocket", (ws) => ListenWebScoket(page, ws)); - }); -} - type Accounts = { user_1: PagesInstance; user_2: PagesInstance; diff --git a/tools/ajaxHooker.js b/tools/ajaxHooker.js index 6c4d7df..68fdf5e 100644 --- a/tools/ajaxHooker.js +++ b/tools/ajaxHooker.js @@ -129,7 +129,7 @@ var ajaxHooker = (function () { emptyFn, errorFn ); - } catch { + } catch (err) { console.error(err); return Promise.resolve(); } diff --git a/tools/apiObserver.js b/tools/apiObserver.js index 985a5aa..eb83230 100644 --- a/tools/apiObserver.js +++ b/tools/apiObserver.js @@ -28,7 +28,7 @@ }); ajaxHooker.filter([ { - url: /^https:\/\/(test|demo)\.cloudlong.cn/, + url: /^https:\/\/(oc-test|demo)\.cloudlong.cn/, async: true, }, ]); @@ -42,27 +42,33 @@ document.body.appendChild(mask); } request.response = (res) => { - if ( - win.apiCounter === 0 && - document.getElementById("networkIdleMask") - ) { - const timeOut = 10000; - const startTime = Date.now(); - const intervalId = setInterval(() => { - if ( - !win.apiCounter && - Date.now() - win.lastResponseEndTime > 80 - ) { - if (document.getElementById("networkIdleMask")) { - document.getElementById("networkIdleMask").remove(); - } - clearInterval(intervalId); - } - if (Date.now() - startTime > timeOut) { - clearInterval(intervalId); - } - }, 80); - } + 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") + ) { + const timeOut = 10000; + const startTime = Date.now(); + const intervalId = setInterval(() => { + if ( + !win.apiCounter && + Date.now() - win.lastResponseEndTime > 80 + ) { + if (document.getElementById("networkIdleMask")) { + document.getElementById("networkIdleMask").remove(); + } + clearInterval(intervalId); + } + if (Date.now() - startTime > timeOut) { + clearInterval(intervalId); + } + }, 80); + } }; } } catch (error) { diff --git a/tools/domObserver.js b/tools/domObserver.js index 8c24ac1..706a52a 100644 --- a/tools/domObserver.js +++ b/tools/domObserver.js @@ -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)] @@ -8,8 +12,8 @@ const domObserver = new MutationObserver((mutationsList) => { ? [(element.disabled = true && element)] : []), ...(element.tagName === "svg" && - !element.ariaHidden && - !element.closest("button") + !element.ariaHidden && + !element.closest("button") ? [(element.closest("div").hidden = true && element.closest("div"))] : []), ...Array.from(element.children || []).flatMap(findAllElementsNeedToDisable), @@ -17,13 +21,20 @@ 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); - elementsToRestore = elementsToRestore.concat(disabledElements); - } + 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; + } + } + } + }); } } } @@ -54,4 +65,4 @@ if (document.body) { window.addEventListener("DOMContentLoaded", () => domObserver.observe(document.body, { childList: true, subtree: true }) ); -} +} \ No newline at end of file