diff --git a/.changeset/dull-snakes-admire.md b/.changeset/dull-snakes-admire.md new file mode 100644 index 00000000..1616a4e9 --- /dev/null +++ b/.changeset/dull-snakes-admire.md @@ -0,0 +1,49 @@ +--- +"webdriver-image-comparison": patch +"@wdio/visual-service": patch +--- + +Optimize Mobile and Emulated device support + +## ๐Ÿ› Bugfixes + +### #969 Fix layer injection on mobile devices + +On some devices the layer injection, to determine the exact position of the webview, was failing. It exceeded the appium timeout and returned an error like + +```logs +[1] [0-0] 2025-05-23T08:04:11.788Z INFO webdriver: COMMAND getUrl() +[1] [0-0] 2025-05-23T08:04:11.789Z INFO webdriver: [GET] https://hub-cloud.browserstack.com/wd/hub/session/xxxxx/url +[1] [0-0] 2025-05-23T08:04:12.036Z INFO webdriver: RESULT about:blank +[1] [0-0] 2025-05-23T08:04:12.038Z INFO webdriver: COMMAND navigateTo("data:text/html;base64,CiAgICAgICAgPG .... LONG LIST OF CHARACTERS=") +[1] [0-0] 2025-05-23T08:04:12.038Z INFO webdriver: [POST] https://hub-cloud.browserstack.com/wd/hub/session/xxxx/url +[1] [0-0] 2025-05-23T08:04:12.038Z INFO webdriver: DATA { +[1] [0-0] url: 'data:text/html;base64,CiAgICAgICAgPGh0bWw.... LONG LIST OF CHARACTERS=' +[1] [0-0] } +[1] [0-0] 2025-05-23T08:05:42.132Z ERROR @wdio/utils:shim: Error: WebDriverError: The operation was aborted due to timeout when running "url" with method "POST" and args "{"url":"data:text/html;base64,CiAgICAgICAgPGh0b.... LONG LIST OF CHARACTERS="}" +[1] [0-0] at FetchRequest._libRequest (file:///xxxxxxx/node_modules/webdriver/build/node.js:1836:13) +[1] [0-0] 2025-05-23T08:05:42.132Z DEBUG @wdio/utils:shim: Finished to run "before" hook in 91147ms +[1] [0-0] at process.processTicksAndRejections (node:internal/process/task_queues:95:5) +[1] [0-0] at async FetchRequest._request (file:///C:/xxxxxx/node_modules/webdriver/build/node.js:1846:20) +[1] [0-0] at Browser.wrapCommandFn (c:/Projects/xxxxxx/node_modules/@wdio/utils/build/index.js:907:23) +[1] [0-0] at Browser.url (c:/Projects/xxxxxxx/node_modules/webdriverio/build/node.js:5682:3) +[1] [0-0] at Browser.wrapCommandFn (c:/Projects/xxxxxx/node_modules/@wdio/utils/build/index.js:907:23) +[1] [0-0] at async loadBase64Html (file:///C:/Projects/xxxxxx/node_modules/webdriver-image-comparison/dist/helpers/utils.js:377:5) +[1] [0-0] at async getMobileViewPortPosition (file:///C:/Projects/xxxxxx/node_modules/webdriver-image-comparison/dist/helpers/utils.js:417:9) +[1] [0-0] at async getMobileInstanceData (file:///C:/Projects/xxxxxx/node_modules/@wdio/visual-service/dist/utils.js:58:28) +[1] [0-0] at async getInstanceData (file:///C:/Projects/xxxxxxx/node_modules/@wdio/visual-service/dist/utils.js:189:77) +[1] [0-0] 2025-05-23T08:05:42.144Z INFO @wdio/browserstack-service: Update job with sessionId xxxxx +``` + +This was caused by the `await url(`data:text/html;base64,${base64Html}`)` that injected the layer. Some browsers couldn't handle the `data:text/html;base64`. + +We now fixed that with a different injection. It was tested on Android/iOS and on Sims/Emus/Real Devices and it worked + +### Improve determining if a device is emulated + +In a previous release we added a function to determine if a device was emulated. This resulted in incorrect screen sizes that were used for the files names for devices. This caused or failing baselines, or new files to be created because the screen sizes were not available +We now improved the check and the correct screen sizes are added again to the file names and made sure that the previous generated base line could be used again. + +## Committers: 1 + +- Wim Selles ([@wswebcreation](https://github.com/wswebcreation)) \ No newline at end of file diff --git a/package.json b/package.json index 1fc788e3..f9ef2f13 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "test.ocr.saucelabs.desktop": "SAUCE=true wdio tests/configs/wdio.ocr.saucelabs.conf.ts", "test.ocr.lambdatest.desktop": "wdio tests/configs/wdio.ocr.lambdatest.conf.ts", "test.unit.coverage": "vitest --coverage", + "test.bs.real.device": "wdio tests/configs/browserstack.real.device.conf.ts", "test.saucelabs.app": "wdio ./tests/configs/wdio.saucelabs.app.conf.ts", "test.saucelabs.emu.app": "cross-env SAUCE_ENV=emu wdio ./tests/configs/wdio.saucelabs.app.conf.ts", "test.saucelabs.sims.app": "cross-env SAUCE_ENV=sims wdio ./tests/configs/wdio.saucelabs.app.conf.ts", @@ -71,6 +72,7 @@ "@vitest/coverage-v8": "^3.1.1", "@vitest/ui": "^3.1.1", "@wdio/appium-service": "^9.13.0", + "@wdio/browserstack-service": "^9.14.0", "@wdio/cli": "^9.14.0", "@wdio/local-runner": "^9.14.0", "@wdio/sauce-service": "^9.14.0", @@ -95,4 +97,4 @@ "wdio-lambdatest-service": "^4.0.0" }, "packageManager": "pnpm@9.15.9+sha256.cf86a7ad764406395d4286a6d09d730711720acc6d93e9dce9ac7ac4dc4a28a7" -} +} \ No newline at end of file diff --git a/packages/webdriver-image-comparison/src/clientSideScripts/__snapshots__/getScreenDimensions.test.ts.snap b/packages/webdriver-image-comparison/src/clientSideScripts/__snapshots__/getScreenDimensions.test.ts.snap index f4291821..ca88f0be 100644 --- a/packages/webdriver-image-comparison/src/clientSideScripts/__snapshots__/getScreenDimensions.test.ts.snap +++ b/packages/webdriver-image-comparison/src/clientSideScripts/__snapshots__/getScreenDimensions.test.ts.snap @@ -29,6 +29,35 @@ exports[`getScreenDimensions > should get the needed screen dimensions 1`] = ` } `; +exports[`getScreenDimensions > should get the needed screen dimensions for a real device 1`] = ` +{ + "dimensions": { + "body": { + "offsetHeight": 0, + "scrollHeight": 0, + }, + "html": { + "clientHeight": 0, + "clientWidth": 0, + "offsetHeight": 0, + "scrollHeight": 0, + "scrollWidth": 0, + }, + "window": { + "devicePixelRatio": 1, + "innerHeight": 768, + "innerWidth": 1024, + "isEmulated": false, + "isLandscape": true, + "outerHeight": 768, + "outerWidth": 1024, + "screenHeight": 0, + "screenWidth": 0, + }, + }, +} +`; + exports[`getScreenDimensions > should get the needed screen dimensions if the outerHeight and outerWidth are set to 0 1`] = ` { "dimensions": { diff --git a/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.test.ts b/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.test.ts index e2321ccb..5004d710 100644 --- a/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.test.ts +++ b/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.test.ts @@ -5,6 +5,16 @@ import { CONFIGURABLE } from '../mocks/mocks.js' import getScreenDimensions from './getScreenDimensions.js' describe('getScreenDimensions', () => { + it('should get the needed screen dimensions for a real device', () => { + Object.defineProperty(window, 'matchMedia', { + value: vi.fn().mockImplementation(() => ({ + matches: true, + })), + ...CONFIGURABLE, + }) + expect(getScreenDimensions(true)).toMatchSnapshot() + }) + it('should get the needed screen dimensions', () => { Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => ({ @@ -12,7 +22,7 @@ describe('getScreenDimensions', () => { })), ...CONFIGURABLE, }) - expect(getScreenDimensions()).toMatchSnapshot() + expect(getScreenDimensions(false)).toMatchSnapshot() }) it('should get the needed screen dimensions if the outerHeight and outerWidth are set to 0', () => { @@ -27,7 +37,7 @@ describe('getScreenDimensions', () => { ...CONFIGURABLE, }) - expect(getScreenDimensions()).toMatchSnapshot() + expect(getScreenDimensions(false)).toMatchSnapshot() }) it('should return zeroed dimensions if the document attributes are null', () => { @@ -40,7 +50,7 @@ describe('getScreenDimensions', () => { ...CONFIGURABLE, }) - expect(getScreenDimensions()).toMatchSnapshot() + expect(getScreenDimensions(false)).toMatchSnapshot() }) it('should detect mobile emulation and return emulated dimensions', () => { @@ -67,7 +77,7 @@ describe('getScreenDimensions', () => { ...CONFIGURABLE, }) - const dimensions = getScreenDimensions() + const dimensions = getScreenDimensions(false) Object.defineProperty(window, 'screen', { value: originalScreen, @@ -106,7 +116,7 @@ describe('getScreenDimensions', () => { ...CONFIGURABLE, }) - const dimensions = getScreenDimensions() + const dimensions = getScreenDimensions(false) Object.defineProperty(window, 'screen', { value: originalScreen, @@ -145,7 +155,7 @@ describe('getScreenDimensions', () => { ...CONFIGURABLE, }) - const dimensions = getScreenDimensions() + const dimensions = getScreenDimensions(false) Object.defineProperty(window, 'screen', { value: originalScreen, diff --git a/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.ts b/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.ts index 2808eb4e..a406d9b4 100644 --- a/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.ts +++ b/packages/webdriver-image-comparison/src/clientSideScripts/getScreenDimensions.ts @@ -3,7 +3,7 @@ import type { ScreenDimensions } from './screenDimensions.interfaces.js' /** * Get all the screen dimensions */ -export default function getScreenDimensions(): ScreenDimensions { +export default function getScreenDimensions(isMobile: boolean): ScreenDimensions { // We need to determine if the screen is emulated, because that would return different values const width = window.innerWidth const height = window.innerHeight @@ -11,6 +11,7 @@ export default function getScreenDimensions(): ScreenDimensions { const minEdge = Math.min(width, height) const maxEdge = Math.max(width, height) const isLikelyEmulated = + !isMobile && // Only check for emulated on desktop dpr >= 2 && // High-DPI signal minEdge <= 800 && // Catch phones/tablets in portrait/landscape maxEdge <= 1280 && // Conservative max for emulated tablet sizes diff --git a/packages/webdriver-image-comparison/src/helpers/utils.test.ts b/packages/webdriver-image-comparison/src/helpers/utils.test.ts index e4c250d9..e03ed7a4 100644 --- a/packages/webdriver-image-comparison/src/helpers/utils.test.ts +++ b/packages/webdriver-image-comparison/src/helpers/utils.test.ts @@ -673,27 +673,25 @@ describe('utils', () => { }) describe('loadBase64Html', () => { - const mockUrl = vi.fn() const mockExecutor = vi.fn() afterEach(() => { vi.clearAllMocks() }) - it('should call url with base64 html and skip executor for Android', async () => { - await loadBase64Html({ executor: mockExecutor, isIOS: false, url: mockUrl }) + it('should call executor with blob URL creation for all platforms', async () => { + await loadBase64Html({ executor: mockExecutor, isIOS: false }) - expect(mockUrl).toHaveBeenCalledTimes(1) - expect(mockUrl.mock.calls[0][0]).toMatch(/^data:text\/html;base64,/) - expect(mockExecutor).not.toHaveBeenCalled() + expect(mockExecutor).toHaveBeenCalledTimes(1) + expect(mockExecutor).toHaveBeenCalledWith(expect.any(Function), expect.any(String)) }) - it('should call url with base64 html and call executor for iOS', async () => { - await loadBase64Html({ executor: mockExecutor, isIOS: true, url: mockUrl }) + it('should call executor with blob URL creation and checkMetaTag for iOS', async () => { + await loadBase64Html({ executor: mockExecutor, isIOS: true }) - expect(mockUrl).toHaveBeenCalledTimes(1) - expect(mockUrl.mock.calls[0][0]).toMatch(/^data:text\/html;base64,/) - expect(mockExecutor).toHaveBeenCalledWith(checkMetaTag) + expect(mockExecutor).toHaveBeenCalledTimes(2) + expect(mockExecutor).toHaveBeenNthCalledWith(1, expect.any(Function), expect.any(String)) + expect(mockExecutor).toHaveBeenNthCalledWith(2, checkMetaTag) }) }) @@ -733,7 +731,7 @@ describe('utils', () => { logWarnMock.mockRestore() }) - it('should throw the error if itโ€™s not a known Appium command error', async () => { + it('should throw the error if it\'s not a known Appium command error', async () => { const executor = vi.fn().mockRejectedValueOnce(new Error('Some unexpected error')) await expect(executeNativeClick({ executor, isIOS: false, ...coords })) @@ -767,6 +765,7 @@ describe('utils', () => { it('should return correct device rectangles for iOS WebView flow', async () => { mockExecutor + .mockResolvedValueOnce(undefined) // executor for the blob (loadBase64Html) .mockResolvedValueOnce(undefined) // checkMetaTag (loadBase64Html) .mockResolvedValueOnce(undefined) // injectWebviewOverlay .mockResolvedValueOnce(undefined) // nativeClick @@ -778,7 +777,7 @@ describe('utils', () => { }) expect(mockGetUrl).toHaveBeenCalled() - expect(mockUrl).toHaveBeenCalledTimes(2) + expect(mockUrl).toHaveBeenCalledTimes(1) expect(mockExecutor).toHaveBeenCalledWith(injectWebviewOverlay, false) expect(mockExecutor).toHaveBeenCalledWith(getMobileWebviewClickAndDimensions, '[data-test="ics-overlay"]') diff --git a/packages/webdriver-image-comparison/src/helpers/utils.ts b/packages/webdriver-image-comparison/src/helpers/utils.ts index cd0bc4ee..05187150 100644 --- a/packages/webdriver-image-comparison/src/helpers/utils.ts +++ b/packages/webdriver-image-comparison/src/helpers/utils.ts @@ -432,7 +432,7 @@ export async function getMobileScreenSize({ /** * Load a base64 HTML page in the browser */ -export async function loadBase64Html({ executor, isIOS, url }: {executor:Executor, isIOS:boolean, url:any}): Promise { +export async function loadBase64Html({ executor, isIOS }: {executor:Executor, isIOS:boolean}): Promise { const htmlContent = ` @@ -457,9 +457,11 @@ export async function loadBase64Html({ executor, isIOS, url }: {executor:Executo ` - const base64Html = Buffer.from(htmlContent).toString('base64') - - await url(`data:text/html;base64,${base64Html}`) + await executor((htmlContent) => { + const blob = new Blob([htmlContent], { type: 'text/html' }) + const blobUrl = URL.createObjectURL(blob) + window.location.href = blobUrl + }, htmlContent) if (isIOS) { await executor(checkMetaTag) @@ -519,7 +521,7 @@ export async function getMobileViewPortPosition({ if (!isNativeContext && (isIOS || (isAndroid && nativeWebScreenshot))) { const currentUrl = await getUrl() // 1. Load a base64 HTML page - await loadBase64Html({ executor, isIOS, url }) + await loadBase64Html({ executor, isIOS }) // 2. Inject an overlay on top of the webview with an event listener that stores the click position in the webview await executor(injectWebviewOverlay, isAndroid) // 3. Click on the overlay in the center of the screen with a native click diff --git a/packages/webdriver-image-comparison/src/methods/instanceData.ts b/packages/webdriver-image-comparison/src/methods/instanceData.ts index 96c1ecfb..a609c29f 100644 --- a/packages/webdriver-image-comparison/src/methods/instanceData.ts +++ b/packages/webdriver-image-comparison/src/methods/instanceData.ts @@ -22,7 +22,7 @@ export default async function getEnrichedInstanceData( addShadowPadding: boolean, ): Promise { // Get the current browser data - const browserData = await executor(getScreenDimensions) + const browserData = await executor(getScreenDimensions, instanceOptions.isMobile) const { addressBarShadowPadding, toolBarShadowPadding, browserName, nativeWebScreenshot, platformName } = instanceOptions // Determine some constants diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0af0eb33..8ea4f8e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: '@wdio/appium-service': specifier: ^9.13.0 version: 9.13.0 + '@wdio/browserstack-service': + specifier: ^9.14.0 + version: 9.14.0(@wdio/cli@9.14.0) '@wdio/cli': specifier: ^9.14.0 version: 9.14.0 @@ -463,6 +466,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@browserstack/ai-sdk-node@1.5.17': + resolution: {integrity: sha512-odjnFulpBeF64UGHA+bIxkIcALYvEPznTl4U0hRT1AFfn4FqT+4wQdPBYnSnlc2XWTedv4zCDvbp4AFrtKXHEw==} + '@changesets/apply-release-plan@7.0.12': resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} @@ -1653,6 +1659,21 @@ packages: '@octokit/types@13.10.0': resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + '@open-draft/until@1.0.3': + resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==} + + '@percy/appium-app@2.1.0': + resolution: {integrity: sha512-XVigKgAcXEerIch3Ufngac07gOH4KnfTDp/xyPujDyjvAZSWfIyIRnojmfbLEs2HnZEnmFFoEMX6ZB4Tk0SO/Q==} + engines: {node: '>=14'} + + '@percy/sdk-utils@1.30.11': + resolution: {integrity: sha512-EuJB8R+ZS7Q/LpdiCoXM+MIGuBVDtvH0vIYQRK6abu0QlD11ra30eN4beD3zTOIe15CgOawzGFLs3cv1noX5fg==} + engines: {node: '>=14'} + + '@percy/selenium-webdriver@2.2.3': + resolution: {integrity: sha512-dVUsgKkDUYvv7+jN4S4HuwSoYxb7Up0U7dM3DRj3/XzLp3boZiyTWAdFdOGS8R5eSsiY5UskTcGQKmGqHRle1Q==} + engines: {node: '>=14'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1943,6 +1964,9 @@ packages: '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/gitconfiglocal@2.0.3': + resolution: {integrity: sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==} + '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} @@ -2285,6 +2309,12 @@ packages: resolution: {integrity: sha512-ocqRlWuF1jah7wPi42lqV6523trJeeKrcdQVExgp70E8czu7gtQr3LKJTFjO76eTzz43WhA4s9QP9bAEM481hA==} engines: {node: '>=18.20.0'} + '@wdio/browserstack-service@9.14.0': + resolution: {integrity: sha512-wPl4wjQTkTqY08uGTkVJhv4qEQMlb/pbPn0B/JQRaL5JfBDxalOQixBNfuSPyZtsdzL13irg76IzaRkEuS0qhQ==} + engines: {node: '>=18.20.0'} + peerDependencies: + '@wdio/cli': ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + '@wdio/cli@9.14.0': resolution: {integrity: sha512-bS7015C54gZMs0gZrCQPFes5CqoVGtwTzlSzJBy0HBx94E8AQw/6feH1FyN6asz4uyfifpUbdpq7Q/7CkaEnGA==} engines: {node: '>=18.20.0'} @@ -2702,6 +2732,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserstack-local@1.5.6: + resolution: {integrity: sha512-s0GadAkyE1XHxnmymb9atogTZbA654bcFpqGkcYEtYPaPvuvVfSXR0gw8ojn0I0Td2HEMJcGtdrkBjb1Fi/HmQ==} + buffer-alloc-unsafe@1.1.0: resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} @@ -3085,6 +3118,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csv-writer@1.6.0: + resolution: {integrity: sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -3318,6 +3354,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + duplexify@3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} @@ -3637,6 +3676,9 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} + event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -3846,6 +3888,10 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + formdata-node@5.0.1: + resolution: {integrity: sha512-8xnIjMYGKPj+rY2BTbAmpqVpi8der/2FT4d9f7J32FlsCpO5EzZPq3C/N56zdv8KweHzVF6TGijsS1JT6r1H2g==} + engines: {node: '>= 14.17'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -3861,6 +3907,9 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -3984,12 +4033,19 @@ packages: gifwrap@0.10.1: resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} + git-repo-info@2.1.1: + resolution: {integrity: sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==} + engines: {node: '>= 4.0'} + git-up@8.1.0: resolution: {integrity: sha512-cT2f5ERrhFDMPS5wLHURcjRiacC8HonX0zIAWBTwHv1fS6HheP902l6pefOX/H9lNmvCHDwomw0VeN7nhg5bxg==} git-url-parse@16.0.0: resolution: {integrity: sha512-Y8iAF0AmCaqXc6a5GYgPQW9ESbncNLOL+CeQAJRhmWUOmnPkKpBYeWYp4mFd3LA5j53CdGDdslzX12yEBVHQQg==} + gitconfiglocal@2.1.0: + resolution: {integrity: sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4114,6 +4170,9 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + headers-utils@1.2.5: + resolution: {integrity: sha512-DAzV5P/pk3wTU/8TLZN+zFTDv4Xa1QDTU8pRvovPetcOMbmqq8CwsAvZBLPZHH6usxyy31zMp7I4aCYb6XIf6w==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -4445,6 +4504,9 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-running@2.1.0: + resolution: {integrity: sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==} + is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} @@ -4875,6 +4937,9 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} engines: {node: '>=0.10.0'} @@ -5238,6 +5303,9 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-request-interceptor@0.6.3: + resolution: {integrity: sha512-8I2V7H2Ch0NvW7qWcjmS0/9Lhr0T6x7RD6PDirhvWEkUQvy83x8BA4haYMr09r/rig7hcgYSjYh6cd4U7G1vLA==} + node-tesseract-ocr@2.2.1: resolution: {integrity: sha512-Q9cD79JGpPNQBxbi1fV+OAsTxYKLpx22sagsxSyKbu1u+t6UarApf5m32uVc8a5QAP1Wk7fIPN0aJFGGEE9DyQ==} engines: {node: '>=10'} @@ -5603,6 +5671,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + peek-readable@4.1.0: resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} engines: {node: '>=8'} @@ -5824,6 +5895,11 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} @@ -6101,6 +6177,11 @@ packages: rgb2hex@0.2.5: resolution: {integrity: sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==} + rimraf@2.5.4: + resolution: {integrity: sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@6.0.1: resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} engines: {node: 20 || >=22} @@ -6375,6 +6456,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + split@1.0.1: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} @@ -6416,6 +6500,9 @@ packages: resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} engines: {node: '>= 0.10.0'} + stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} @@ -6429,6 +6516,9 @@ packages: streamx@2.22.0: resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + strict-event-emitter@0.1.0: + resolution: {integrity: sha512-8hSYfU+WKLdNcHVXJ0VxRXiPESalzRe7w1l8dg9+/22Ry+iZQUoQuoJ27R30GMD1TiyYINWsIEGY05WrskhSKw==} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -6578,6 +6668,10 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + temp-fs@0.9.9: + resolution: {integrity: sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==} + engines: {node: '>=0.8.0'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -6655,6 +6749,10 @@ packages: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + to-buffer@1.1.1: resolution: {integrity: sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==} @@ -6956,6 +7054,14 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -7132,6 +7238,10 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + webdriver@9.13.0: resolution: {integrity: sha512-cnUX4NBmUC5DtqbNPVE41HkXgdFHNOeBy8OM2SWt8R8TrT2bxP03VGVgl9g/ykg8YqgLeTxUHtIQnk7JN2EXzg==} engines: {node: '>=18.20.0'} @@ -7372,6 +7482,10 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yauzl@3.2.0: + resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} + engines: {node: '>=12'} + yazl@2.5.1: resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} @@ -7633,6 +7747,13 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@browserstack/ai-sdk-node@1.5.17': + dependencies: + axios: 1.8.4 + uuid: 9.0.1 + transitivePeerDependencies: + - debug + '@changesets/apply-release-plan@7.0.12': dependencies: '@changesets/config': 3.1.1 @@ -8884,6 +9005,22 @@ snapshots: dependencies: '@octokit/openapi-types': 24.2.0 + '@open-draft/until@1.0.3': {} + + '@percy/appium-app@2.1.0': + dependencies: + '@percy/sdk-utils': 1.30.11 + tmp: 0.2.3 + + '@percy/sdk-utils@1.30.11': {} + + '@percy/selenium-webdriver@2.2.3': + dependencies: + '@percy/sdk-utils': 1.30.11 + node-request-interceptor: 0.6.3 + transitivePeerDependencies: + - supports-color + '@pkgjs/parseargs@0.11.0': optional: true @@ -9216,6 +9353,8 @@ snapshots: '@types/jsonfile': 6.1.4 '@types/node': 22.14.0 + '@types/gitconfiglocal@2.0.3': {} + '@types/hast@2.3.10': dependencies: '@types/unist': 2.0.11 @@ -9657,6 +9796,35 @@ snapshots: - supports-color - utf-8-validate + '@wdio/browserstack-service@9.14.0(@wdio/cli@9.14.0)': + dependencies: + '@browserstack/ai-sdk-node': 1.5.17 + '@percy/appium-app': 2.1.0 + '@percy/selenium-webdriver': 2.2.3 + '@types/gitconfiglocal': 2.0.3 + '@wdio/cli': 9.14.0 + '@wdio/logger': 9.4.4 + '@wdio/reporter': 9.14.0 + '@wdio/types': 9.14.0 + browserstack-local: 1.5.6 + chalk: 5.4.1 + csv-writer: 1.6.0 + formdata-node: 5.0.1 + git-repo-info: 2.1.1 + gitconfiglocal: 2.1.0 + undici: 6.21.3 + uuid: 10.0.0 + webdriverio: 9.14.0 + winston-transport: 4.9.0 + yauzl: 3.2.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - debug + - puppeteer-core + - supports-color + - utf-8-validate + '@wdio/cli@9.14.0': dependencies: '@types/node': 20.17.48 @@ -10271,6 +10439,16 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.5) + browserstack-local@1.5.6: + dependencies: + agent-base: 6.0.2 + https-proxy-agent: 5.0.1 + is-running: 2.1.0 + ps-tree: 1.2.0 + temp-fs: 0.9.9 + transitivePeerDependencies: + - supports-color + buffer-alloc-unsafe@1.1.0: {} buffer-alloc@1.2.0: @@ -10693,6 +10871,8 @@ snapshots: csstype@3.1.3: {} + csv-writer@1.6.0: {} + damerau-levenshtein@1.0.8: {} data-uri-to-buffer@3.0.1: {} @@ -10879,6 +11059,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer@0.1.2: {} + duplexify@3.7.1: dependencies: end-of-stream: 1.4.4 @@ -11408,6 +11590,16 @@ snapshots: '@types/node': 22.15.19 require-like: 0.1.2 + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + event-target-shim@5.0.1: {} events@3.3.0: {} @@ -11708,6 +11900,11 @@ snapshots: format@0.2.2: {} + formdata-node@5.0.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -11718,6 +11915,8 @@ snapshots: fresh@0.5.2: {} + from@0.1.7: {} + fs-constants@1.0.0: {} fs-extra@10.1.0: @@ -11858,6 +12057,8 @@ snapshots: image-q: 4.0.0 omggif: 1.0.10 + git-repo-info@2.1.1: {} + git-up@8.1.0: dependencies: is-ssh: 1.4.1 @@ -11867,6 +12068,10 @@ snapshots: dependencies: git-up: 8.1.0 + gitconfiglocal@2.1.0: + dependencies: + ini: 1.3.8 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -12034,6 +12239,8 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 + headers-utils@1.2.5: {} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -12346,6 +12553,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-running@2.1.0: {} + is-set@2.0.3: {} is-shared-array-buffer@1.0.4: @@ -12796,6 +13005,8 @@ snapshots: make-error@1.3.6: {} + map-stream@0.1.0: {} + markdown-extensions@1.1.1: {} matchit@1.1.0: @@ -13319,6 +13530,15 @@ snapshots: node-releases@2.0.19: {} + node-request-interceptor@0.6.3: + dependencies: + '@open-draft/until': 1.0.3 + debug: 4.4.1(supports-color@8.1.1) + headers-utils: 1.2.5 + strict-event-emitter: 0.1.0 + transitivePeerDependencies: + - supports-color + node-tesseract-ocr@2.2.1: {} normalize-package-data@2.5.0: @@ -13738,6 +13958,10 @@ snapshots: pathval@2.0.0: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + peek-readable@4.1.0: {} peek-stream@1.1.3: @@ -13942,6 +14166,10 @@ snapshots: proxy-from-env@1.1.0: {} + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + pump@2.0.1: dependencies: end-of-stream: 1.4.4 @@ -14307,6 +14535,10 @@ snapshots: rgb2hex@0.2.5: {} + rimraf@2.5.4: + dependencies: + glob: 7.2.3 + rimraf@6.0.1: dependencies: glob: 11.0.1 @@ -14649,6 +14881,10 @@ snapshots: split2@4.2.0: {} + split@0.3.3: + dependencies: + through: 2.3.8 + split@1.0.1: dependencies: through: 2.3.8 @@ -14679,6 +14915,10 @@ snapshots: stream-buffers@3.0.3: {} + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + stream-shift@1.0.3: {} stream-slice@0.1.2: {} @@ -14692,6 +14932,8 @@ snapshots: optionalDependencies: bare-events: 2.5.4 + strict-event-emitter@0.1.0: {} + strict-uri-encode@2.0.0: {} string-hash@1.1.3: {} @@ -14915,6 +15157,10 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + temp-fs@0.9.9: + dependencies: + rimraf: 2.5.4 + term-size@2.2.1: {} tesseract.js-core@5.1.1: {} @@ -14992,6 +15238,8 @@ snapshots: dependencies: os-tmpdir: 1.0.2 + tmp@0.2.3: {} + to-buffer@1.1.1: {} to-regex-range@5.0.1: @@ -15309,6 +15557,10 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + + uuid@9.0.1: {} + uvu@0.5.6: dependencies: dequal: 2.0.3 @@ -15506,6 +15758,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} + webdriver@9.13.0: dependencies: '@types/node': 20.17.48 @@ -15822,6 +16076,11 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + yauzl@3.2.0: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + yazl@2.5.1: dependencies: buffer-crc32: 0.2.13 diff --git a/tests/configs/browserstack.real.device.conf.ts b/tests/configs/browserstack.real.device.conf.ts new file mode 100644 index 00000000..77e8ec3c --- /dev/null +++ b/tests/configs/browserstack.real.device.conf.ts @@ -0,0 +1,40 @@ +import type { Capabilities } from '@wdio/types' +import { join } from 'node:path' +import { config as sharedBrowserStackConfig } from './wdio.browserstack.shared.conf.ts' + +const mobileSpecs = join(process.cwd(), './tests/specs/mobile.web.spec.ts') + +export const config: WebdriverIO.Config = { + ...sharedBrowserStackConfig, + // ============ + // Capabilities + // ============ + capabilities: [ + { + 'bstack:options': { + osVersion: '18', + deviceName: 'iPhone 14', + projectName: 'ProjectName', + buildName: 'BuildName', + sessionName: 'SessionName', + appiumVersion: '2.15.0', + realMobile: 'true', + debug: 'true', + networkLogs: 'true', + consoleLogs: 'verbose', + deviceOrientation: 'PORTRAIT', + }, + browserName: 'iPhone-14', + platformName: 'iOS', + specs: [mobileSpecs], + 'wdio-ics:options': { + logName: 'browserstack-real-device-iPhone-14', + commands: [ + // 'checkScreen', + // 'checkFullPageScreen', + 'checkElement', + ], + }, + } as Capabilities.BrowserStackCapabilities, + ], +} diff --git a/tests/configs/wdio.browserstack.shared.conf.ts b/tests/configs/wdio.browserstack.shared.conf.ts new file mode 100644 index 00000000..f6413397 --- /dev/null +++ b/tests/configs/wdio.browserstack.shared.conf.ts @@ -0,0 +1,51 @@ +import { join } from 'node:path' +import { config as sharedConfig } from './wdio.shared.conf.ts' +import type { VisualServiceOptions } from '@wdio/visual-service' + +export const config: WebdriverIO.Config = { + ...sharedConfig, + // =================== + // Test Configurations + // =================== + specFileRetries: 3, + // Wait for 8 min, then a new session should be created + // and the queue should be empty + connectionRetryTimeout: 8 * 60 * 1000, + // ============================ + // Browserstack specific config + // ============================ + user: process.env.BROWSERSTACK_USERNAME, + key: process.env.BROWSERSTACK_ACCESS_KEY, + // ============ + // Capabilities + // ============ + capabilities: [], + // ======== + // Services + // ======== + services: [ + 'browserstack', + // =================== + // Image compare setup + // =================== + [ + 'visual', + { + addIOSBezelCorners: true, + baselineFolder: join( + process.cwd(), + 'tmp/browserstackBaseline/' + ), + formatImageName: '{tag}-{logName}-{width}x{height}', + screenshotPath: join(process.cwd(), '.tmp/'), + savePerInstance: true, + blockOutStatusBar: true, + blockOutToolBar: true, + blockOutSideBar: true, + createJsonReportFiles: true, + rawMisMatchPercentage: !!process.env.RAW_MISMATCH || false, + enableLayoutTesting: true, + } satisfies VisualServiceOptions, + ], + ], +} diff --git a/tests/specs/mobile.web.spec.ts b/tests/specs/mobile.web.spec.ts index fe382957..b29a7451 100644 --- a/tests/specs/mobile.web.spec.ts +++ b/tests/specs/mobile.web.spec.ts @@ -7,10 +7,24 @@ describe('@wdio/visual-service mobile web', () => { // Get the commands that need to be executed // 0 means all, otherwise it will only execute the commands that are specified const wdioIcsCommands = driver.requestedCapabilities['wdio-ics:options'].commands - const deviceName = (driver.requestedCapabilities['lt:options'] || driver.requestedCapabilities).deviceName - const platformName = (driver.requestedCapabilities['lt:options'] || driver.requestedCapabilities).platformName.toLowerCase() === 'android' ? 'Android' : 'iOS' - const platformVersion = (driver.requestedCapabilities['lt:options'] || driver.requestedCapabilities).platformVersion - const orientation = (driver.requestedCapabilities['lt:options']?.deviceOrientation || driver.requestedCapabilities.orientation).toLowerCase() + const deviceName = ( + driver.requestedCapabilities['lt:options']?.deviceOrientation || + driver.requestedCapabilities['bstack:options']?.deviceOrientation || + driver.requestedCapabilities.orientation + ).deviceName + const platformName = ( + driver.requestedCapabilities['lt:options']?.platformName || + driver.requestedCapabilities.platformName + ).toLowerCase() === 'android' ? 'Android' : 'iOS' + const platformVersion = + driver.requestedCapabilities['lt:options']?.platformVersion || + driver.requestedCapabilities['bstack:options']?.osVersion || + driver.requestedCapabilities.platformVersion + const orientation = ( + driver.requestedCapabilities['lt:options']?.deviceOrientation || + driver.requestedCapabilities['bstack:options']?.deviceOrientation || + driver.requestedCapabilities.orientation + ).toLowerCase() beforeEach(async () => { await browser.url('') diff --git a/tests/tsconfig.json b/tests/tsconfig.json index d37e9917..b67aacc6 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -5,6 +5,7 @@ "target": "es2022", "lib": ["es2022", "dom"], "types": [ + "@wdio/browserstack-service", "@wdio/globals/types", "@wdio/mocha-framework", "@wdio/sauce-service",