diff --git a/.gitignore b/.gitignore index 83cafe1f5bf536cfe85c243feaaa81f33823bc36..e68bcae25ded6012ce136a5f60a2305f260a7b13 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ .project unpackage/ .DS_Store -.hbuilderx/ \ No newline at end of file +.hbuilderx/ +__image_snapshots__/ \ No newline at end of file diff --git a/jest-setup.js b/jest-setup.js index 8299fe7a9dc3cf45df648a7c734a5723480a4e41..c5b8872c3bf029e27902f4acef9ea85934f18d1a 100644 --- a/jest-setup.js +++ b/jest-setup.js @@ -1,3 +1,25 @@ -const { toMatchImageSnapshot } = require('jest-image-snapshot') +const path = require('path'); +const { + configureToMatchImageSnapshot +} = require('jest-image-snapshot'); -expect.extend({ toMatchImageSnapshot }) +const hbuilderx_version = process.env.HX_Version +const uniTestPlatformInfo = process.env.uniTestPlatformInfo ? process.env.uniTestPlatformInfo.replace(/\s/g,'_') : '' +const folderName = `__image_snapshots__/${hbuilderx_version}/__${uniTestPlatformInfo}__` +let environment = 'official' +if(hbuilderx_version.includes('dev')){ + environment = 'dev' +}else if(hbuilderx_version.includes('alpha')){ + environment = 'alpha' +} +const baseFolderName = `__image_snapshots__/base/${environment}/__${uniTestPlatformInfo}__` + +expect.extend({ + toMatchImageSnapshot: configureToMatchImageSnapshot({ + customSnapshotIdentifier(args) { + return args.currentTestName.replace(/\//g, '-').replace(' ', '-'); + }, + customSnapshotsDir: path.join(__dirname, baseFolderName), + customDiffDir: path.join(__dirname, `${folderName}/`, 'diff'), + }), +}); diff --git a/pages.json b/pages.json index 11ecc623c44501581924f8e9af80147fd197ec78..489729b89d44eab942a5a6dd90e4e61905671ef1 100644 --- a/pages.json +++ b/pages.json @@ -631,7 +631,21 @@ "navigationBarTitleText" : "teleport", "enablePullDownRefresh" : false } - } + }, + { + "path": "pages/webview-screenshot-comparison/webview-screenshot-comparison", + "style": { + "navigationBarTitleText": "截图对比测试", + "navigationStyle": "custom" + } + }, + { + "path": "pages/webview-screenshot/webview-screenshot", + "style": { + "navigationBarTitleText": "webview 截图测试", + "navigationStyle": "custom" + } + } ], "tabBar": { "color": "#7A7E83", diff --git a/pages/webview-screenshot-comparison/webview-screenshot-comparison.test.js b/pages/webview-screenshot-comparison/webview-screenshot-comparison.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6460726a2760c8360747f784ca848707dab198d6 --- /dev/null +++ b/pages/webview-screenshot-comparison/webview-screenshot-comparison.test.js @@ -0,0 +1,245 @@ +jest.setTimeout(10000000); + +const pages = [ + 'pages/tab-bar/options-api', + 'pages/tab-bar/composition-api', + 'pages/app-instance/index/index', + 'pages/app-instance/globalProperties/globalProperties', + 'pages/built-in-component/keep-alive/keep-alive', + 'pages/directive/v-bind/v-bind', + 'pages/directive/v-bind/v-bind-class', + 'pages/directive/v-bind/v-bind-style', + 'pages/directive/v-bind/v-bind-props', + 'pages/directive/v-for/v-for', + 'pages/directive/v-for/v-for-item-click', + 'pages/directive/v-for/v-for-item-v-if', + 'pages/directive/v-for/v-for-item-v-show', + 'pages/directive/v-for/v-for-v-for', + 'pages/directive/v-if/v-if', + 'pages/directive/v-model/v-model', + 'pages/directive/v-on/v-on', + 'pages/directive/v-memo/v-memo', + 'pages/directive/v-pre/v-pre', + 'pages/directive/v-show/v-show', + 'pages/directive/v-slot/v-slot', + 'pages/directive/v-html/v-html', + 'pages/lifecycle/page/page', + 'pages/lifecycle/component/component', + 'pages/component-instance/data/data', + 'pages/component-instance/props/props', + 'pages/component-instance/el/el', + 'pages/component-instance/options/options', + 'pages/component-instance/slots/slots', + 'pages/component-instance/refs/refs', + 'pages/component-instance/attrs/attrs', + 'pages/component-instance/watch-function/watch-function', + 'pages/component-instance/watch-function/watch-array', + 'pages/component-instance/emit-function/emit-function', + + 'pages/component-instance/nextTick-function/nextTick-function', + 'pages/component-instance/methods/call-method-uni-element', + + 'pages/component-instance/methods/call-method-other', + 'pages/component-instance/circular-reference/circular-reference', + 'pages/state/data/data', + 'pages/state/methods/methods', + 'pages/state/props/props', + 'pages/state/computed/computed', + 'pages/state/watch/watch', + 'pages/rendering/slots/slots', + 'pages/rendering/template/template', + 'pages/rendering/unrecognized-component/unrecognized-component', + 'pages/rendering/component/component', + 'pages/rendering/render/render', + + 'pages/composition/provide/provide', + 'pages/composition/provide/provide-page2', + 'pages/composition/inject/inject', + 'pages/composition/setup/setup', + 'pages/examples/nested-component-communication/nested-component-communication', + 'pages/examples/set-custom-child-component-root-node-class/set-custom-child-component-root-node-class', + 'pages/composition-api/basic/define-slots/define-slots', + + // 新增 + 'pages/composition-api/basic/define-emits/define-emits', + 'pages/composition-api/basic/define-expose/define-expose', + + 'pages/composition-api/basic/use-attrs/use-attrs', + 'pages/composition-api/basic/use-slots/use-slots', + 'pages/composition-api/reactivity/ref/ref', + 'pages/composition-api/reactivity/computed/computed', + 'pages/composition-api/reactivity/reactive/reactive', + 'pages/composition-api/reactivity/readonly/readonly', + 'pages/composition-api/reactivity/watch/watch', + 'pages/composition-api/reactivity/watch-effect/watch-effect', + 'pages/composition-api/reactivity/watch-post-effect/watch-post-effect', + 'pages/composition-api/reactivity/watch-sync-effect/watch-sync-effect', + 'pages/composition-api/reactivity/is-ref/is-ref', + 'pages/composition-api/reactivity/un-ref/un-ref', + 'pages/composition-api/reactivity/to-ref/to-ref', + 'pages/composition-api/reactivity/is-proxy/is-proxy', + 'pages/composition-api/reactivity/is-reactive/is-reactive', + 'pages/composition-api/reactivity/is-readonly/is-readonly', + 'pages/composition-api/reactivity/shallow-ref/shallow-ref', + 'pages/composition-api/reactivity/trigger-ref/trigger-ref', + 'pages/composition-api/reactivity/custom-ref/custom-ref', + 'pages/composition-api/reactivity/shallow-reactive/shallow-reactive', + 'pages/composition-api/reactivity/shallow-readonly/shallow-readonly', + 'pages/composition-api/reactivity/to-raw/to-raw', + 'pages/composition-api/reactivity/mark-raw/mark-raw', + 'pages/composition-api/reactivity/effect-scope/effect-scope', + 'pages/composition-api/reactivity/get-current-scope/get-current-scope', + 'pages/composition-api/reactivity/on-scope-dispose/on-scope-dispose', + 'pages/composition-api/lifecycle/page-lifecycle/page-lifecycle', + 'pages/composition-api/lifecycle/component-lifecycle/component-lifecycle', + 'pages/composition-api/dependency-injection/provide/provide', + 'pages/built-in-component/teleport/teleport' + + + // web暂不支持 + // 'pages/composition/mixins/mixins', + // 'pages/composition/mixins/mixins-page2', + // 'pages/directive/v-once/v-once', + // 'pages/component-instance/parent/parent', + // 'pages/component-instance/root/root', + // 'pages/composition-api/basic/define-model/define-model' + // 'pages/composition-api/basic/define-options/define-option' + + // 动态内容 + // 'pages/component-instance/force-update/force-update', + + // 空白页面无内容 + // 'pages/component-instance/methods/call-method-easycom-uni-modules', + // 'pages/component-instance/methods/call-method-easycom', + +] + +const childToParentPagesMap = new Map([ + +]); + +const customNavigationPages = [ + +] + +const needAdbScreenshotPages = [ + 'pages/tab-bar/options-api', + 'pages/tab-bar/composition-api' +]; + +const needAdbScreenshot = (url) => { + return needAdbScreenshotPages.includes(url); +}; + +const PAGE_PATH = + "/pages/webview-screenshot-comparison/webview-screenshot-comparison"; + +describe("shot-compare", () => { + let shouldCompareScreenShot = false + if (process.env.uniTestPlatformInfo.startsWith('android')) { + let version = process.env.uniTestPlatformInfo + version = parseInt(version.split(" ")[1]) + shouldCompareScreenShot = version > 9 + } + + if (!shouldCompareScreenShot) { + it("other platform not support", async () => { + expect(1).toBe(1); + }); + return + } + + let page = null; + let pageIndex = 0; + let baseSrc = ""; + beforeAll(async () => { + page = await program.reLaunch(PAGE_PATH); + await page.waitFor(500); + + // set webview-screenshot-comparison page baseSrc + baseSrc = + process.env.UNI_WEB_SERVICE_URL ? `${process.env.UNI_WEB_SERVICE_URL}/#/` : + "http://192.168.31.223:5173/#/"; + page.setData({ + baseSrc, + }); + }); + + beforeEach(async () => { + page = await program.reLaunch(PAGE_PATH); + await page.waitFor(500); + }); + afterEach(() => { + pageIndex++; + }); + + test.each(pages)("%s", async () => { + const isNeedAdbScreenshot = needAdbScreenshot(pages[pageIndex]); + const isCustomNavigationBar = customNavigationPages.includes(pages[pageIndex]); + const { + statusBarHeight, + devicePixelRatio + } = await page.data(); + const screenshotParams = { + fullPage: true, + adb: isNeedAdbScreenshot, + // adb 截图时跳过状态栏 + area: { + x: 0, + y: statusBarHeight * devicePixelRatio, + }, + } + const screenshotPath = `__webview__${pages[pageIndex].replace(/\//g, "-")}`; + + // web in webview screenshot + // 加载依赖页面 + if (childToParentPagesMap.get(pages[pageIndex])) { + await page.setData({ + src: `${baseSrc}${childToParentPagesMap.get(pages[pageIndex])}`, + isLoaded: false + }); + await page.waitFor(async () => { + const isLoaded = await page.data("isLoaded"); + return isLoaded || Date.now() - startTime > 10000; + }); + await page.waitFor(200); + } + await page.setData({ + src: `${baseSrc}${pages[pageIndex]}`, + isLoaded: false, + isCustomNavigationBar, + }); + + const startTime = Date.now(); + await page.waitFor(async () => { + const isLoaded = await page.data("isLoaded"); + return isLoaded || Date.now() - startTime > 3000; + }); + await page.waitFor(3000); + + // web 端非 adb 截图时设置 offsetY 移除导航栏 + const webSnapshot = await program.screenshot({ + ...screenshotParams, + id: 'webview-screenshot-comparison', + offsetY: `${isCustomNavigationBar ? 0 : 44}` + }); + expect(webSnapshot).toMatchImageSnapshot({ + customSnapshotIdentifier() { + return screenshotPath; + }, + }); + + // app-android page screenshot comparison + const navigateMethod = pages[pageIndex].startsWith("pages/tab-bar") ? + "switchTab" : + "navigateTo"; + page = await program[navigateMethod](`/${pages[pageIndex]}`); + await page.waitFor(500); + const appAndroidSnapshot = await program.screenshot(screenshotParams); + expect(appAndroidSnapshot).toMatchImageSnapshot({ + customSnapshotIdentifier() { + return screenshotPath; + }, + }); + }); +}); diff --git a/pages/webview-screenshot-comparison/webview-screenshot-comparison.uvue b/pages/webview-screenshot-comparison/webview-screenshot-comparison.uvue new file mode 100644 index 0000000000000000000000000000000000000000..450215eb264ecc4cd0211e62e44065750bcbb3cc --- /dev/null +++ b/pages/webview-screenshot-comparison/webview-screenshot-comparison.uvue @@ -0,0 +1,76 @@ + + + + \ No newline at end of file diff --git a/pages/webview-screenshot/webview-screenshot.test.js b/pages/webview-screenshot/webview-screenshot.test.js new file mode 100644 index 0000000000000000000000000000000000000000..50265ba8215a2575eef6a7df84a1d2d3a1362450 --- /dev/null +++ b/pages/webview-screenshot/webview-screenshot.test.js @@ -0,0 +1,228 @@ +jest.setTimeout(10000000); + +const pages = [ + 'pages/tab-bar/options-api', + 'pages/tab-bar/composition-api', + 'pages/app-instance/index/index', + 'pages/app-instance/globalProperties/globalProperties', + 'pages/built-in-component/keep-alive/keep-alive', + 'pages/directive/v-bind/v-bind', + 'pages/directive/v-bind/v-bind-class', + 'pages/directive/v-bind/v-bind-style', + 'pages/directive/v-bind/v-bind-props', + 'pages/directive/v-for/v-for', + 'pages/directive/v-for/v-for-item-click', + 'pages/directive/v-for/v-for-item-v-if', + 'pages/directive/v-for/v-for-item-v-show', + 'pages/directive/v-for/v-for-v-for', + 'pages/directive/v-if/v-if', + 'pages/directive/v-model/v-model', + 'pages/directive/v-on/v-on', + 'pages/directive/v-memo/v-memo', + 'pages/directive/v-pre/v-pre', + 'pages/directive/v-show/v-show', + 'pages/directive/v-slot/v-slot', + 'pages/directive/v-html/v-html', + 'pages/lifecycle/page/page', + 'pages/lifecycle/component/component', + 'pages/component-instance/data/data', + 'pages/component-instance/props/props', + 'pages/component-instance/el/el', + 'pages/component-instance/options/options', + 'pages/component-instance/slots/slots', + 'pages/component-instance/refs/refs', + 'pages/component-instance/attrs/attrs', + 'pages/component-instance/watch-function/watch-function', + 'pages/component-instance/watch-function/watch-array', + 'pages/component-instance/emit-function/emit-function', + + 'pages/component-instance/nextTick-function/nextTick-function', + 'pages/component-instance/methods/call-method-uni-element', + + 'pages/component-instance/methods/call-method-other', + 'pages/component-instance/circular-reference/circular-reference', + 'pages/state/data/data', + 'pages/state/methods/methods', + 'pages/state/props/props', + 'pages/state/computed/computed', + 'pages/state/watch/watch', + 'pages/rendering/slots/slots', + 'pages/rendering/template/template', + 'pages/rendering/unrecognized-component/unrecognized-component', + 'pages/rendering/component/component', + 'pages/rendering/render/render', + + 'pages/composition/provide/provide', + 'pages/composition/provide/provide-page2', + 'pages/composition/inject/inject', + 'pages/composition/setup/setup', + 'pages/examples/nested-component-communication/nested-component-communication', + 'pages/examples/set-custom-child-component-root-node-class/set-custom-child-component-root-node-class', + 'pages/composition-api/basic/define-slots/define-slots', + + // 新增 + 'pages/composition-api/basic/define-emits/define-emits', + 'pages/composition-api/basic/define-expose/define-expose', + + 'pages/composition-api/basic/use-attrs/use-attrs', + 'pages/composition-api/basic/use-slots/use-slots', + 'pages/composition-api/reactivity/ref/ref', + 'pages/composition-api/reactivity/computed/computed', + 'pages/composition-api/reactivity/reactive/reactive', + 'pages/composition-api/reactivity/readonly/readonly', + 'pages/composition-api/reactivity/watch/watch', + 'pages/composition-api/reactivity/watch-effect/watch-effect', + 'pages/composition-api/reactivity/watch-post-effect/watch-post-effect', + 'pages/composition-api/reactivity/watch-sync-effect/watch-sync-effect', + 'pages/composition-api/reactivity/is-ref/is-ref', + 'pages/composition-api/reactivity/un-ref/un-ref', + 'pages/composition-api/reactivity/to-ref/to-ref', + 'pages/composition-api/reactivity/is-proxy/is-proxy', + 'pages/composition-api/reactivity/is-reactive/is-reactive', + 'pages/composition-api/reactivity/is-readonly/is-readonly', + 'pages/composition-api/reactivity/shallow-ref/shallow-ref', + 'pages/composition-api/reactivity/trigger-ref/trigger-ref', + 'pages/composition-api/reactivity/custom-ref/custom-ref', + 'pages/composition-api/reactivity/shallow-reactive/shallow-reactive', + 'pages/composition-api/reactivity/shallow-readonly/shallow-readonly', + 'pages/composition-api/reactivity/to-raw/to-raw', + 'pages/composition-api/reactivity/mark-raw/mark-raw', + 'pages/composition-api/reactivity/effect-scope/effect-scope', + 'pages/composition-api/reactivity/get-current-scope/get-current-scope', + 'pages/composition-api/reactivity/on-scope-dispose/on-scope-dispose', + 'pages/composition-api/lifecycle/page-lifecycle/page-lifecycle', + 'pages/composition-api/lifecycle/component-lifecycle/component-lifecycle', + 'pages/composition-api/dependency-injection/provide/provide', + 'pages/built-in-component/teleport/teleport' + + + // web暂不支持 + // 'pages/composition/mixins/mixins', + // 'pages/composition/mixins/mixins-page2', + // 'pages/directive/v-once/v-once', + // 'pages/component-instance/parent/parent', + // 'pages/component-instance/root/root', + // 'pages/composition-api/basic/define-model/define-model' + // 'pages/composition-api/basic/define-options/define-option' + + // 动态内容 + // 'pages/component-instance/force-update/force-update', + + // 空白页面无内容 + // 'pages/component-instance/methods/call-method-easycom-uni-modules', + // 'pages/component-instance/methods/call-method-easycom', + +] + +const childToParentPagesMap = new Map([]); + +const customNavigationPages = [ + +] + +const needAdbScreenshotPages = [ + 'pages/tab-bar/options-api', + 'pages/tab-bar/composition-api', +]; + +const needAdbScreenshot = (url) => { + return needAdbScreenshotPages.includes(url); +}; + +const PAGE_PATH = + "/pages/webview-screenshot/webview-screenshot"; + +describe("shot-compare", () => { + let shouldCompareScreenShot = false + if (process.env.uniTestPlatformInfo.startsWith('android')) { + let version = process.env.uniTestPlatformInfo + version = parseInt(version.split(" ")[1]) + shouldCompareScreenShot = version > 9 + } + + if (!shouldCompareScreenShot) { + it("other platform not support", async () => { + expect(1).toBe(1); + }); + return + } + let page = null; + let pageIndex = 0; + let baseSrc = ""; + beforeAll(async () => { + page = await program.reLaunch(PAGE_PATH); + await page.waitFor(500); + + // set webview-screenshot page baseSrc + baseSrc = + process.env.UNI_WEB_SERVICE_URL ? `${process.env.UNI_WEB_SERVICE_URL}/#/` : + "http://192.168.31.223:5173/#/"; + page.setData({ + baseSrc, + }); + }); + + beforeEach(async () => { + page = await program.reLaunch(PAGE_PATH); + await page.waitFor(500); + }); + afterEach(() => { + pageIndex++; + }); + + test.each(pages)("%s", async () => { + const isNeedAdbScreenshot = needAdbScreenshot(pages[pageIndex]); + const isCustomNavigation = customNavigationPages.includes(pages[pageIndex]); + const { + headerHeight, + devicePixelRatio + } = await page.data(); + const screenshotParams = { + fullPage: true, + adb: isNeedAdbScreenshot, + // adb 截图时跳过状态栏 + area: { + x: 0, + y: headerHeight * devicePixelRatio, + }, + } + const screenshotPath = `webview-shot__${pages[pageIndex].replace(/\//g, "-")}`; + + // web in webview screenshot + // 加载依赖页面 + if (childToParentPagesMap.get(pages[pageIndex])) { + await page.setData({ + src: `${baseSrc}${childToParentPagesMap.get(pages[pageIndex])}`, + isLoaded: false + }); + await page.waitFor(async () => { + const isLoaded = await page.data("isLoaded"); + return isLoaded || Date.now() - startTime > 10000; + }); + await page.waitFor(200); + } + await page.setData({ + src: `${baseSrc}${pages[pageIndex]}`, + isLoaded: false, + needRemoveWebHead: !isNeedAdbScreenshot + }); + + const startTime = Date.now(); + await page.waitFor(async () => { + const isLoaded = await page.data("isLoaded"); + return isLoaded || Date.now() - startTime > 3000; + }); + await page.waitFor(4000); + + // web 端非 adb 截图时设置 offsetY 移除导航栏 + const webSnapshot = await program.screenshot({ + ...screenshotParams, + offsetY: `${isCustomNavigation ? 0 : headerHeight}` + }); + expect(webSnapshot).toMatchImageSnapshot({ + customSnapshotIdentifier() { + return screenshotPath; + }, + }); + }); +}); \ No newline at end of file diff --git a/pages/webview-screenshot/webview-screenshot.uvue b/pages/webview-screenshot/webview-screenshot.uvue new file mode 100644 index 0000000000000000000000000000000000000000..8fe984ccab1596019dfebd49bfc5d68153dda427 --- /dev/null +++ b/pages/webview-screenshot/webview-screenshot.uvue @@ -0,0 +1,57 @@ + + + +