@wdio/visual-service 6.4.0 β†’ 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,228 @@
1
1
  # @wdio/visual-service
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - bfe6aca: ## πŸ’₯ BREAKING CHANGES
8
+
9
+ ### πŸ§ͺ Web Screenshot Strategy Now Uses BiDi by Default
10
+
11
+ #### What was the problem?
12
+
13
+ Screenshots taken via WebDriver's traditional protocol often lacked precision:
14
+
15
+ - Emulated devices didn't reflect true resolutions
16
+ - Device Pixel Ratio (DPR) was often lost
17
+ - Images were cropped or downscaled
18
+
19
+ #### What changed?
20
+
21
+ All screenshot-related methods now use the **WebDriver BiDi protocol** by default (if supported by the browser), enabling:
22
+
23
+ βœ… Native support for emulated and high-DPR devices
24
+ βœ… Better fidelity in screenshot size and clarity
25
+ βœ… Faster, browser-native screenshots via [`browsingContext.captureScreenshot`](https://w3c.github.io/webdriver-bidi/#command-browsingContext-captureScreenshot)
26
+
27
+ The following methods now use BiDi:
28
+
29
+ - `saveScreen` / `checkScreen`
30
+ - `saveElement` / `checkElement`
31
+ - `saveFullPageScreen` / `checkFullPageScreen`
32
+
33
+ #### What’s the impact?
34
+
35
+ ⚠️ **Existing baselines may no longer match.**
36
+ Because BiDi screenshots are **sharper** and **match device settings more accurately**, even a small difference in resolution or DPR can cause mismatches.
37
+
38
+ > If you rely on existing baseline images, you'll need to regenerate them to avoid false positives.
39
+
40
+ #### Want to keep using the legacy method?
41
+
42
+ You can disable BiDi screenshots globally or per test using the `enableLegacyScreenshotMethod` flag:
43
+
44
+ **Globally in `wdio.conf.ts`:**
45
+
46
+ ```ts
47
+ import { join } from "node:path";
48
+
49
+ export const config = {
50
+ services: [
51
+ [
52
+ "visual",
53
+ {
54
+ baselineFolder: join(process.cwd(), "./localBaseline/"),
55
+ debug: true,
56
+ formatImageName: "{tag}-{logName}-{width}x{height}",
57
+ screenshotPath: join(process.cwd(), ".tmp/"),
58
+ enableLegacyScreenshotMethod: true, // πŸ‘ˆ fallback to W3C-based screenshots
59
+ },
60
+ ],
61
+ ],
62
+ };
63
+ ```
64
+
65
+ **Or per test:**
66
+
67
+ ```ts
68
+ it("should compare an element successfully using legacy screenshots", async function () {
69
+ await expect($(".hero__title-logo")).toMatchElementSnapshot(
70
+ "legacyScreenshotLogo",
71
+ { enableLegacyScreenshotMethod: true } // πŸ‘ˆ fallback to W3C-based screenshots
72
+ );
73
+ });
74
+ ```
75
+
76
+ ## πŸ› Bug Fixes
77
+
78
+ - βœ… [#916](https://github.com/webdriverio/visual-testing/issues/916): Visual Testing Screenshot Behavior Changed in Emulated Devices
79
+
80
+ ## Committers: 1
81
+
82
+ - Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
83
+
84
+ ### Patch Changes
85
+
86
+ - Updated dependencies [bfe6aca]
87
+ - webdriver-image-comparison@9.0.0
88
+
89
+ ## 7.0.0
90
+
91
+ ### Major Changes
92
+
93
+ - 42956e4: ## πŸ’₯ BREAKING CHANGES
94
+
95
+ ### πŸ” Viewport Screenshot Logic Reworked for Mobile Web & Hybrid Apps
96
+
97
+ #### What was the problem?
98
+
99
+ Screenshots for mobile devices were inconsistent due to platform differences. iOS captures the entire device screen (including status and address bars), while Android (using ChromeDriver) only captures the webview, unless the capability `"appium:nativeWebScreenshot": true` is used.
100
+
101
+ #### What changed?
102
+
103
+ We’ve reimplemented the logic to correctly handle both platforms by default.
104
+ This fix addresses [[#747](https://github.com/webdriverio/visual-testing/pull/747)](https://github.com/webdriverio/visual-testing/pull/747).
105
+
106
+ πŸ’‘ Credit to [Benjamin Karran (@ebekebe)](https://github.com/ebekebe) for pointing us in the right direction to improve this logic!
107
+
108
+ #### What’s the advantage?
109
+
110
+ βœ… More **accurate full-page and element screenshots** on both Android and iOS.
111
+ ⚠️ But this change may **break your current baselines**, especially on Android and iOS.
112
+
113
+ ***
114
+
115
+ ### 🍏 iOS Element Screenshot Strategy Changed
116
+
117
+ #### What was the problem?
118
+
119
+ iOS element screenshots were previously cut from full-device screenshots, which could lead to misalignment or off-by-a-few-pixels issues.
120
+
121
+ #### What changed?
122
+
123
+ We now use the element screenshot endpoint directly.
124
+
125
+ #### What’s the advantage?
126
+
127
+ βœ… More accurate iOS element screenshots.
128
+ ⚠️ But again, this may affect your existing baselines.
129
+
130
+ ***
131
+
132
+ ### πŸ–₯️ New Full-Page Screenshot Strategy for **Desktop Web**
133
+
134
+ #### What was the problem?
135
+
136
+ The "previous" scroll-and-stitch method simulated user interaction by scrolling the page, waiting, taking a screenshot, and repeating until the entire page was captured.
137
+ This works well for **lazy-loaded content**, but it is **slow and unstable** on other pages.
138
+
139
+ #### What changed?
140
+
141
+ We now use WebDriver BiDi’s [`[browsingContext.captureScreenshot](https://webdriver.io/docs/api/webdriverBidi#browsingcontextcapturescreenshot)`] to capture **full-page screenshots in one go**. This is the new **default strategy for desktop web browsers**.
142
+
143
+ πŸ“Œ **Mobile platforms (iOS/Android)** still use the scroll-and-stitch approach for now.
144
+
145
+ #### What’s the advantage?
146
+
147
+ βœ… Execution time reduced by **50%+**
148
+ βœ… Logic is greatly simplified
149
+ βœ… More consistent and stable results on static or non-lazy pages
150
+ πŸ“Έ ![Example](https://github.com/user-attachments/assets/394ad1d6-bbc7-42dd-b93b-ff7eb5a80429)
151
+
152
+ **Still want the old scroll-and-stitch behavior or need fullpage screenshots for pages who have lazy-loading?**
153
+
154
+ Use the `userBasedFullPageScreenshot` option to simulate user-like scrolling. This remains the **better choice for pages with lazy-loading**:
155
+
156
+ ```ts
157
+ // wdio.conf.ts
158
+ services: [
159
+ [
160
+ "visual",
161
+ {
162
+ userBasedFullPageScreenshot: true,
163
+ },
164
+ ],
165
+ ];
166
+ ```
167
+
168
+ Or per test:
169
+
170
+ ```ts
171
+ await expect(browser).toMatchFullPageSnapshot("homepage", {
172
+ userBasedFullPageScreenshot: true,
173
+ });
174
+ ```
175
+
176
+ ***
177
+
178
+ ## πŸ’… Polish
179
+
180
+ ### ⚠️ Deprecated Root-Level Compare Options
181
+
182
+ #### What was the problem?
183
+
184
+ Compare options were allowed at the root level of the service config, making them harder to group or discover.
185
+
186
+ #### What changed?
187
+
188
+ You now get a warning if you still use root-level keys. Please move them under the `compareOptions` property instead.
189
+
190
+ **Example warning:**
191
+
192
+ ```log
193
+ WARN The following root-level compare options are deprecated and should be moved under 'compareOptions':
194
+ - blockOutStatusBar
195
+ - ignoreColors
196
+ In the next major version, these options will be removed from the root level.
197
+ ```
198
+
199
+ πŸ“˜ See: [[compareOptions docs](https://webdriver.io/docs/visual-testing/service-options#compare-options)](https://webdriver.io/docs/visual-testing/service-options#compare-options)
200
+
201
+ ***
202
+
203
+ ## πŸ› Bug Fixes
204
+
205
+ - βœ… [[#747](https://github.com/your-repo/issues/747)](https://github.com/your-repo/issues/747): Fixed incorrect mobile webview context data.
206
+
207
+ ***
208
+
209
+ ## πŸ”§ Other
210
+
211
+ - πŸ†™ Updated dependencies
212
+ - πŸ§ͺ Improved test coverage
213
+ - πŸ“Έ Refreshed image baselines
214
+
215
+ ***
216
+
217
+ ## Committers: 1
218
+
219
+ - Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
220
+
221
+ ### Patch Changes
222
+
223
+ - Updated dependencies [42956e4]
224
+ - webdriver-image-comparison@8.0.0
225
+
3
226
  ## 6.4.0
4
227
 
5
228
  ### Minor Changes
@@ -0,0 +1,15 @@
1
+ import type { DeviceRectangles } from 'webdriver-image-comparison';
2
+ export declare class ContextManager {
3
+ #private;
4
+ private cachedViewport;
5
+ constructor(browser: WebdriverIO.Browser);
6
+ getViewportContext(): DeviceRectangles;
7
+ setViewPortContext(viewport: DeviceRectangles): void;
8
+ get browser(): WebdriverIO.Browser;
9
+ get needsUpdate(): boolean;
10
+ markForUpdate(): void;
11
+ setCurrentContext(context: string): void;
12
+ getCurrentContext(): Promise<string | undefined>;
13
+ get isNativeContext(): boolean;
14
+ }
15
+ //# sourceMappingURL=contextManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextManager.d.ts","sourceRoot":"","sources":["../src/contextManager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAMlE,qBAAa,cAAc;;IAKvB,OAAO,CAAC,cAAc,CAAsC;gBAEhD,OAAO,EAAE,WAAW,CAAC,OAAO;IAOxC,kBAAkB,IAAI,gBAAgB;IAItC,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAKpD,IAAI,OAAO,IAAI,WAAW,CAAC,OAAO,CAEjC;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,aAAa,IAAI,IAAI;IAIrB,iBAAiB,CAAC,OAAO,EAAE,MAAM;IAO3B,iBAAiB;IAIvB,IAAI,eAAe,YAElB;CA0BJ"}
@@ -0,0 +1,71 @@
1
+ import logger from '@wdio/logger';
2
+ import { DEVICE_RECTANGLES } from 'webdriver-image-comparison';
3
+ import { getNativeContext } from './utils.js';
4
+ const log = logger('@wdio/visual-service:ContextManager');
5
+ export class ContextManager {
6
+ #browser;
7
+ #needsUpdate = false;
8
+ #currentContext;
9
+ #isNativeContext;
10
+ cachedViewport = DEVICE_RECTANGLES;
11
+ constructor(browser) {
12
+ this.#browser = browser;
13
+ const capabilities = this.#browser.requestedCapabilities;
14
+ this.#isNativeContext = getNativeContext({ capabilities, isMobile: this.#browser.isMobile });
15
+ this.#browser.on('result', this.#onCommandResult.bind(this));
16
+ }
17
+ getViewportContext() {
18
+ return this.cachedViewport;
19
+ }
20
+ setViewPortContext(viewport) {
21
+ this.cachedViewport = viewport;
22
+ this.#needsUpdate = false;
23
+ }
24
+ get browser() {
25
+ return this.#browser;
26
+ }
27
+ get needsUpdate() {
28
+ return this.#needsUpdate;
29
+ }
30
+ markForUpdate() {
31
+ this.#needsUpdate = true;
32
+ }
33
+ setCurrentContext(context) {
34
+ this.#currentContext = context;
35
+ if (this.#browser.isMobile) {
36
+ this.#isNativeContext = context ? context === 'NATIVE_APP' : this.#isNativeContext;
37
+ }
38
+ }
39
+ async getCurrentContext() {
40
+ return this.#currentContext;
41
+ }
42
+ get isNativeContext() {
43
+ return this.#isNativeContext;
44
+ }
45
+ async #onCommandResult(event) {
46
+ const commands = ['switchAppiumContext', 'switchContext', 'setOrientation'];
47
+ if (commands.includes(event.command)) {
48
+ const { body: { name, orientation }, result } = event;
49
+ // Check if result exists and is not an error object
50
+ // @ts-expect-error
51
+ const isSuccess = result && !result.error && typeof result === 'object';
52
+ if (isSuccess) {
53
+ if (event.command === 'setOrientation') {
54
+ log.info(`Device rotation to "${orientation}" detected, context will need recalculation.`);
55
+ this.markForUpdate();
56
+ }
57
+ else {
58
+ if (name && name !== 'NATIVE_APP') {
59
+ log.info(`Context changed to "${name}", context will need recalculation.`);
60
+ this.markForUpdate();
61
+ }
62
+ // Set the context to the current context
63
+ this.setCurrentContext(name);
64
+ }
65
+ }
66
+ else {
67
+ log.warn('Orientation set failed: \n', result, '\nWe could not recalibrate the context');
68
+ }
69
+ }
70
+ }
71
+ }
package/dist/service.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import type { Frameworks } from '@wdio/types';
2
2
  import { BaseClass } from 'webdriver-image-comparison';
3
3
  import type { VisualServiceOptions } from './types.js';
4
+ import { ContextManager } from './contextManager.js';
4
5
  export default class WdioImageComparisonService extends BaseClass {
5
6
  #private;
6
- private _isNativeContext;
7
+ private _contextManager?;
8
+ private _contextManagers?;
7
9
  constructor(options: VisualServiceOptions, _: WebdriverIO.Capabilities, config: WebdriverIO.Config);
8
10
  /**
9
11
  * Set up the service if users want to use it in standalone mode
@@ -12,6 +14,6 @@ export default class WdioImageComparisonService extends BaseClass {
12
14
  before(capabilities: WebdriverIO.Capabilities, _specs: string[], browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): Promise<void>;
13
15
  beforeTest(test: Frameworks.Test): Promise<void>;
14
16
  beforeScenario(world: Frameworks.World): void;
15
- afterCommand(commandName: string, _args: string[], result: number | string, error: any): void;
17
+ get contextManager(): ContextManager;
16
18
  }
17
19
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EACH,SAAS,EAWZ,MAAM,4BAA4B,CAAA;AAYnC,OAAO,KAAK,EAA+C,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAenG,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,SAAS;;IAM7D,OAAO,CAAC,gBAAgB,CAA+B;gBAE3C,OAAO,EAAE,oBAAoB,EAAE,CAAC,EAAE,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM;IAOlG;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IAIzE,MAAM,CACR,YAAY,EAAE,WAAW,CAAC,YAAY,EACtC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IAgC3D,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI;IAOtC,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK;IAItC,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;CA2WzF"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EACH,SAAS,EAWZ,MAAM,4BAA4B,CAAA;AAkBnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAepD,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,SAAS;;IAM7D,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,gBAAgB,CAAC,CAAyC;gBAEtD,OAAO,EAAE,oBAAoB,EAAE,CAAC,EAAE,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM;IAMlG;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IAIzE,MAAM,CACR,YAAY,EAAE,WAAW,CAAC,YAAY,EACtC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IA2B3D,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI;IAOtC,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK;IAqctC,IAAI,cAAc,IAAI,cAAc,CAKnC;CACJ"}
package/dist/service.js CHANGED
@@ -3,10 +3,12 @@ import { expect } from '@wdio/globals';
3
3
  import { dirname, normalize, resolve } from 'node:path';
4
4
  import { BaseClass, checkElement, checkFullPageScreen, checkScreen, saveElement, saveFullPageScreen, saveScreen, saveTabbablePage, checkTabbablePage, FOLDERS, DEFAULT_TEST_CONTEXT, } from 'webdriver-image-comparison';
5
5
  import { SevereServiceError } from 'webdriverio';
6
- import { determineNativeContext, enrichTestContext, getFolders, getInstanceData, getNativeContext } from './utils.js';
6
+ import { enrichTestContext, getFolders, getInstanceData, getNativeContext, isBiDiScreenshotSupported, } from './utils.js';
7
7
  import { toMatchScreenSnapshot, toMatchFullPageSnapshot, toMatchElementSnapshot, toMatchTabbablePageSnapshot } from './matcher.js';
8
8
  import { waitForStorybookComponentToBeLoaded } from './storybook/utils.js';
9
9
  import { PAGE_OPTIONS_MAP } from './constants.js';
10
+ import { ContextManager } from './contextManager.js';
11
+ import { wrapWithContext } from './wrapWithContext.js';
10
12
  const log = logger('@wdio/visual-service');
11
13
  const elementCommands = { saveElement, checkElement };
12
14
  const pageCommands = {
@@ -24,11 +26,11 @@ export default class WdioImageComparisonService extends BaseClass {
24
26
  #currentFilePath;
25
27
  #testContext;
26
28
  #browser;
27
- _isNativeContext;
29
+ _contextManager;
30
+ _contextManagers = new Map();
28
31
  constructor(options, _, config) {
29
32
  super(options);
30
33
  this.#config = config;
31
- this._isNativeContext = undefined;
32
34
  this.#testContext = DEFAULT_TEST_CONTEXT;
33
35
  }
34
36
  /**
@@ -39,7 +41,6 @@ export default class WdioImageComparisonService extends BaseClass {
39
41
  }
40
42
  async before(capabilities, _specs, browser) {
41
43
  this.#browser = browser;
42
- this._isNativeContext = determineNativeContext(this.#browser);
43
44
  if (!this.#browser.isMultiremote) {
44
45
  log.info('Adding commands to global browser');
45
46
  await this.#addCommandsToBrowser(this.#browser);
@@ -47,9 +48,6 @@ export default class WdioImageComparisonService extends BaseClass {
47
48
  else {
48
49
  await this.#extendMultiremoteBrowser(capabilities);
49
50
  }
50
- if (browser.isMultiremote) {
51
- this.#setupMultiremoteContextListener();
52
- }
53
51
  /**
54
52
  * add custom matcher for visual comparison when expect has been added.
55
53
  * this is not the case in standalone mode
@@ -75,13 +73,6 @@ export default class WdioImageComparisonService extends BaseClass {
75
73
  beforeScenario(world) {
76
74
  this.#testContext = this.#getTestContext(world);
77
75
  }
78
- afterCommand(commandName, _args, result, error) {
79
- // This is for the cases where in the E2E tests we switch to a WEBVIEW or back to NATIVE_APP context
80
- if (commandName === 'getContext' && error === undefined && typeof result === 'string') {
81
- // Multiremote logic is handled in the `before` method during an event listener
82
- this._isNativeContext = this.#browser?.isMultiremote ? this._isNativeContext : result.includes('NATIVE');
83
- }
84
- }
85
76
  #getBaselineFolder() {
86
77
  const isDefaultBaselineFolder = normalize(FOLDERS.DEFAULT.BASE) === this.folders.baselineFolder;
87
78
  const baselineFolder = (isDefaultBaselineFolder && this.#currentFilePath ? this.#currentFilePath : this.folders.baselineFolder);
@@ -108,6 +99,8 @@ export default class WdioImageComparisonService extends BaseClass {
108
99
  for (const browserName of browserNames) {
109
100
  log.info(`Adding commands to Multi Browser: ${browserName}`);
110
101
  const browserInstance = browser.getInstance(browserName);
102
+ const contextManager = new ContextManager(browserInstance);
103
+ this._contextManagers?.set(browserName, contextManager);
111
104
  await this.#addCommandsToBrowser(browserInstance);
112
105
  }
113
106
  /**
@@ -130,123 +123,181 @@ export default class WdioImageComparisonService extends BaseClass {
130
123
  * Add commands to the "normal" browser object
131
124
  */
132
125
  async #addCommandsToBrowser(currentBrowser) {
133
- const instanceData = await getInstanceData(currentBrowser);
134
- const isNativeContext = getNativeContext(this.#browser, currentBrowser, this._isNativeContext);
126
+ this._contextManager = new ContextManager(currentBrowser);
127
+ currentBrowser.visualService = this;
128
+ const instanceData = await getInstanceData({
129
+ currentBrowser,
130
+ initialDeviceRectangles: this._contextManager.getViewportContext(),
131
+ isNativeContext: this._contextManager.isNativeContext,
132
+ });
133
+ // Update the context manager with the current viewport
134
+ this._contextManager.setViewPortContext(instanceData.deviceRectangles);
135
135
  for (const [commandName, command] of Object.entries(elementCommands)) {
136
- this.#addElementCommand(currentBrowser, commandName, command, instanceData, isNativeContext);
136
+ this.#addElementCommand(currentBrowser, commandName, command, instanceData);
137
137
  }
138
138
  for (const [commandName, command] of Object.entries(pageCommands)) {
139
- this.#addPageCommand(currentBrowser, commandName, command, instanceData, isNativeContext);
139
+ this.#addPageCommand(currentBrowser, commandName, command, instanceData);
140
140
  }
141
141
  }
142
142
  /**
143
143
  * Add new element commands to the browser object
144
144
  */
145
- #addElementCommand(browser, commandName, command, instanceData, isNativeContext) {
145
+ #addElementCommand(browser, commandName, command, initialInstanceData) {
146
146
  log.info(`Adding element command "${commandName}" to browser object`);
147
+ const elementOptionsKey = commandName === 'saveElement' ? 'saveElementOptions' : 'checkElementOptions';
147
148
  const self = this;
148
149
  browser.addCommand(commandName, function (element, tag, elementOptions = {}) {
149
- const elementOptionsKey = commandName === 'saveElement' ? 'saveElementOptions' : 'checkElementOptions';
150
- return command({
151
- methods: {
152
- executor: (fn, ...args) => {
153
- return this.execute.bind(browser)(fn, ...args);
154
- },
155
- getElementRect: this.getElementRect.bind(browser),
156
- screenShot: this.takeScreenshot.bind(browser),
157
- takeElementScreenshot: this.takeElementScreenshot.bind(browser),
158
- },
159
- instanceData,
160
- folders: getFolders(elementOptions, self.folders, self.#getBaselineFolder()),
161
- element,
162
- tag,
163
- [elementOptionsKey]: {
164
- wic: self.defaultOptions,
165
- method: elementOptions,
166
- },
167
- isNativeContext,
168
- testContext: enrichTestContext({
169
- commandName,
170
- currentTestContext: self.#testContext,
171
- instanceData,
172
- tag,
173
- })
150
+ const wrapped = wrapWithContext({
151
+ browser,
152
+ command,
153
+ contextManager: self.contextManager,
154
+ getArgs: () => {
155
+ const updatedInstanceData = {
156
+ ...initialInstanceData,
157
+ deviceRectangles: self.contextManager.getViewportContext(),
158
+ };
159
+ const isCurrentContextNative = self.contextManager.isNativeContext;
160
+ return [{
161
+ methods: {
162
+ bidiScreenshot: isBiDiScreenshotSupported(browser) ? this.browsingContextCaptureScreenshot.bind(browser) : undefined,
163
+ executor: (fn, ...args) => {
164
+ return this.execute(fn, ...args);
165
+ },
166
+ getElementRect: this.getElementRect.bind(this),
167
+ getWindowHandle: this.getWindowHandle.bind(browser),
168
+ screenShot: this.takeScreenshot.bind(this),
169
+ takeElementScreenshot: this.takeElementScreenshot.bind(this),
170
+ },
171
+ instanceData: updatedInstanceData,
172
+ folders: getFolders(elementOptions, self.folders, self.#getBaselineFolder()),
173
+ element,
174
+ tag,
175
+ [elementOptionsKey]: {
176
+ wic: self.defaultOptions,
177
+ method: elementOptions,
178
+ },
179
+ isNativeContext: isCurrentContextNative,
180
+ testContext: enrichTestContext({
181
+ commandName,
182
+ currentTestContext: self.#testContext,
183
+ instanceData: updatedInstanceData,
184
+ tag,
185
+ }),
186
+ }];
187
+ }
174
188
  });
189
+ return wrapped.call(this);
175
190
  });
176
191
  }
177
192
  /**
178
193
  * Add new page commands to the browser object
179
194
  */
180
- #addPageCommand(browser, commandName, command, instanceData, isNativeContext) {
195
+ #addPageCommand(browser, commandName, command, initialInstanceData) {
181
196
  log.info(`Adding browser command "${commandName}" to browser object`);
182
197
  const self = this;
183
198
  const pageOptionsKey = PAGE_OPTIONS_MAP[commandName];
184
199
  if (commandName === 'waitForStorybookComponentToBeLoaded') {
185
200
  browser.addCommand(commandName, (options) => waitForStorybookComponentToBeLoaded(options));
201
+ return;
186
202
  }
187
- else {
188
- browser.addCommand(commandName, function (tag, pageOptions = {}) {
189
- return command({
190
- methods: {
191
- executor: (fn, ...args) => {
192
- return this.execute.bind(browser)(fn, ...args);
193
- },
194
- getElementRect: this.getElementRect.bind(browser),
195
- screenShot: this.takeScreenshot.bind(browser),
196
- },
197
- instanceData,
198
- folders: getFolders(pageOptions, self.folders, self.#getBaselineFolder()),
199
- tag,
200
- [pageOptionsKey]: {
201
- wic: self.defaultOptions,
202
- method: pageOptions,
203
- },
204
- isNativeContext,
205
- testContext: enrichTestContext({
206
- commandName,
207
- currentTestContext: self.#testContext,
208
- instanceData,
209
- tag,
210
- })
211
- });
203
+ browser.addCommand(commandName, function (tag, pageOptions = {}) {
204
+ const wrapped = wrapWithContext({
205
+ browser,
206
+ command,
207
+ contextManager: self.contextManager,
208
+ getArgs: () => {
209
+ const updatedInstanceData = {
210
+ ...initialInstanceData,
211
+ deviceRectangles: self.contextManager.getViewportContext()
212
+ };
213
+ const isCurrentContextNative = self.contextManager.isNativeContext;
214
+ return [{
215
+ methods: {
216
+ bidiScreenshot: isBiDiScreenshotSupported(browser) ? this.browsingContextCaptureScreenshot.bind(browser) : undefined,
217
+ executor: (fn, ...args) => {
218
+ return this.execute(fn, ...args);
219
+ },
220
+ getElementRect: this.getElementRect.bind(browser),
221
+ getWindowHandle: this.getWindowHandle.bind(browser),
222
+ screenShot: this.takeScreenshot.bind(browser),
223
+ },
224
+ instanceData: updatedInstanceData,
225
+ folders: getFolders(pageOptions, self.folders, self.#getBaselineFolder()),
226
+ tag,
227
+ [pageOptionsKey]: {
228
+ wic: self.defaultOptions,
229
+ method: pageOptions,
230
+ },
231
+ isNativeContext: isCurrentContextNative,
232
+ testContext: enrichTestContext({
233
+ commandName,
234
+ currentTestContext: self.#testContext,
235
+ instanceData: updatedInstanceData,
236
+ tag,
237
+ }),
238
+ }];
239
+ }
212
240
  });
213
- }
241
+ return wrapped.call(this);
242
+ });
214
243
  }
215
244
  #addMultiremoteElementCommand(browser, browserNames, commandName, command) {
216
245
  log.info(`Adding element command "${commandName}" to Multi browser object`);
217
246
  const self = this;
218
- browser.addCommand(commandName, async function (element, tag, pageOptions = {}) {
247
+ browser.addCommand(commandName, async function (element, tag, elementOptions = {}) {
219
248
  const returnData = {};
220
249
  const elementOptionsKey = commandName === 'saveElement' ? 'saveElementOptions' : 'checkElementOptions';
221
250
  for (const browserName of browserNames) {
222
251
  const browserInstance = browser.getInstance(browserName);
223
- const isNativeContext = getNativeContext(self.#browser, browserInstance, self._isNativeContext);
224
- const instanceData = await getInstanceData(browserInstance);
225
- returnData[browserName] = await command({
226
- methods: {
227
- executor: (fn, ...args) => {
228
- return this.execute.bind(browser)(fn, ...args);
229
- },
230
- getElementRect: browserInstance.getElementRect.bind(browserInstance),
231
- screenShot: browserInstance.takeScreenshot.bind(browserInstance),
232
- takeElementScreenshot: browserInstance.takeElementScreenshot.bind(browserInstance),
233
- },
234
- instanceData,
235
- folders: getFolders(pageOptions, self.folders, self.#getBaselineFolder()),
236
- tag,
237
- element,
238
- [elementOptionsKey]: {
239
- wic: self.defaultOptions,
240
- method: pageOptions,
241
- },
242
- isNativeContext,
243
- testContext: enrichTestContext({
244
- commandName,
245
- currentTestContext: self.#testContext,
246
- instanceData,
247
- tag,
248
- })
252
+ const contextManager = self._contextManagers?.get(browserName);
253
+ if (!contextManager) {
254
+ throw new Error(`No ContextManager found for browser instance: ${browserName}`);
255
+ }
256
+ const isNativeContext = contextManager.isNativeContext;
257
+ const initialInstanceData = await getInstanceData({
258
+ currentBrowser: browserInstance,
259
+ initialDeviceRectangles: contextManager.getViewportContext(),
260
+ isNativeContext
261
+ });
262
+ const wrapped = wrapWithContext({
263
+ browser: browserInstance,
264
+ command,
265
+ contextManager,
266
+ getArgs: () => {
267
+ const updatedInstanceData = {
268
+ ...initialInstanceData,
269
+ deviceRectangles: contextManager.getViewportContext(),
270
+ };
271
+ return [{
272
+ methods: {
273
+ bidiScreenshot: isBiDiScreenshotSupported(browserInstance) ? browserInstance.browsingContextCaptureScreenshot.bind(browserInstance) : undefined,
274
+ executor: (fn, ...args) => {
275
+ return browserInstance.execute(fn, ...args);
276
+ },
277
+ getElementRect: browserInstance.getElementRect.bind(browserInstance),
278
+ getWindowHandle: browserInstance.getWindowHandle.bind(browserInstance),
279
+ screenShot: browserInstance.takeScreenshot.bind(browserInstance),
280
+ takeElementScreenshot: browserInstance.takeElementScreenshot.bind(browserInstance),
281
+ },
282
+ instanceData: updatedInstanceData,
283
+ folders: getFolders(elementOptions, self.folders, self.#getBaselineFolder()),
284
+ tag,
285
+ element,
286
+ [elementOptionsKey]: {
287
+ wic: self.defaultOptions,
288
+ method: elementOptions,
289
+ },
290
+ isNativeContext,
291
+ testContext: enrichTestContext({
292
+ commandName,
293
+ currentTestContext: self.#testContext,
294
+ instanceData: updatedInstanceData,
295
+ tag,
296
+ }),
297
+ }];
298
+ }
249
299
  });
300
+ returnData[browserName] = await wrapped.call(browserInstance);
250
301
  }
251
302
  return returnData;
252
303
  });
@@ -256,59 +307,67 @@ export default class WdioImageComparisonService extends BaseClass {
256
307
  const self = this;
257
308
  if (commandName === 'waitForStorybookComponentToBeLoaded') {
258
309
  browser.addCommand(commandName, waitForStorybookComponentToBeLoaded);
310
+ return;
259
311
  }
260
- else {
261
- browser.addCommand(commandName, async function (tag, pageOptions = {}) {
262
- const returnData = {};
263
- const pageOptionsKey = PAGE_OPTIONS_MAP[commandName];
264
- for (const browserName of browserNames) {
265
- const browserInstance = browser.getInstance(browserName);
266
- const isNativeContext = getNativeContext(self.#browser, browserInstance, self._isNativeContext);
267
- const instanceData = await getInstanceData(browserInstance);
268
- returnData[browserName] = await command({
269
- methods: {
270
- executor: (fn, ...args) => {
271
- return this.execute.bind(browser)(fn, ...args);
272
- },
273
- getElementRect: browserInstance.getElementRect.bind(browserInstance),
274
- screenShot: browserInstance.takeScreenshot.bind(browserInstance),
275
- },
276
- instanceData,
277
- folders: getFolders(pageOptions, self.folders, self.#getBaselineFolder()),
278
- tag,
279
- [pageOptionsKey]: {
280
- wic: self.defaultOptions,
281
- method: pageOptions,
282
- },
283
- isNativeContext,
284
- testContext: enrichTestContext({
285
- commandName,
286
- currentTestContext: self.#testContext,
287
- instanceData,
288
- tag,
289
- })
290
- });
312
+ browser.addCommand(commandName, async function (tag, pageOptions = {}) {
313
+ const returnData = {};
314
+ const pageOptionsKey = PAGE_OPTIONS_MAP[commandName];
315
+ for (const browserName of browserNames) {
316
+ const browserInstance = browser.getInstance(browserName);
317
+ const contextManager = self._contextManagers?.get(browserName);
318
+ if (!contextManager) {
319
+ throw new Error(`No ContextManager found for browser instance: ${browserName}`);
291
320
  }
292
- return returnData;
293
- });
294
- }
295
- }
296
- #setupMultiremoteContextListener() {
297
- const multiremoteBrowser = this.#browser;
298
- const browserInstances = multiremoteBrowser.instances;
299
- for (const instanceName of browserInstances) {
300
- const instance = multiremoteBrowser[instanceName];
301
- instance.on('result', (result) => {
302
- if (result.command === 'getContext') {
303
- const value = result.result.value;
304
- const sessionId = instance.sessionId;
305
- if (typeof this._isNativeContext !== 'object' || this._isNativeContext === null) {
306
- this._isNativeContext = {};
321
+ const isNativeContext = getNativeContext({
322
+ capabilities: browserInstance.requestedCapabilities,
323
+ isMobile: browserInstance.isMobile,
324
+ });
325
+ const initialInstanceData = await getInstanceData({
326
+ currentBrowser: browserInstance,
327
+ initialDeviceRectangles: contextManager.getViewportContext(),
328
+ isNativeContext
329
+ });
330
+ const wrapped = wrapWithContext({
331
+ browser: browserInstance,
332
+ command,
333
+ contextManager,
334
+ getArgs: () => {
335
+ const updatedInstanceData = {
336
+ ...initialInstanceData,
337
+ deviceRectangles: contextManager.getViewportContext()
338
+ };
339
+ const isCurrentContextNative = contextManager.isNativeContext;
340
+ return [{
341
+ methods: {
342
+ bidiScreenshot: isBiDiScreenshotSupported(browserInstance) ? browserInstance.browsingContextCaptureScreenshot.bind(browserInstance) : undefined,
343
+ executor: (fn, ...args) => {
344
+ return browserInstance.execute(fn, ...args);
345
+ },
346
+ getElementRect: browserInstance.getElementRect.bind(browserInstance),
347
+ getWindowHandle: browserInstance.getWindowHandle.bind(browserInstance),
348
+ screenShot: browserInstance.takeScreenshot.bind(browserInstance),
349
+ },
350
+ instanceData: updatedInstanceData,
351
+ folders: getFolders(pageOptions, self.folders, self.#getBaselineFolder()),
352
+ tag,
353
+ [pageOptionsKey]: {
354
+ wic: self.defaultOptions,
355
+ method: pageOptions,
356
+ },
357
+ isNativeContext: isCurrentContextNative,
358
+ testContext: enrichTestContext({
359
+ commandName,
360
+ currentTestContext: self.#testContext,
361
+ instanceData: updatedInstanceData,
362
+ tag,
363
+ }),
364
+ }];
307
365
  }
308
- this._isNativeContext[sessionId] = value.includes('NATIVE');
309
- }
310
- });
311
- }
366
+ });
367
+ returnData[browserName] = await wrapped.call(browserInstance);
368
+ }
369
+ return returnData;
370
+ });
312
371
  }
313
372
  #getTestContext(test) {
314
373
  const framework = this.#config?.framework;
@@ -359,4 +418,10 @@ export default class WdioImageComparisonService extends BaseClass {
359
418
  }
360
419
  throw new SevereServiceError(`Framework ${framework} is not supported by the Visual Service and should be either "mocha", "jasmine" or "cucumber".`);
361
420
  }
421
+ get contextManager() {
422
+ if (!this._contextManager) {
423
+ throw new Error('ContextManager has not been initialized');
424
+ }
425
+ return this._contextManager;
426
+ }
362
427
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { ScreenshotOutput, ImageCompareResult, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, ClassOptions } from 'webdriver-image-comparison';
1
+ import type { ScreenshotOutput, ImageCompareResult, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, ClassOptions, DeviceRectangles, TestContext, InstanceData } from 'webdriver-image-comparison';
2
2
  import type { ChainablePromiseElement } from 'webdriverio';
3
+ import type { ContextManager } from './contextManager.js';
3
4
  type MultiOutput = {
4
5
  [browserName: string]: ScreenshotOutput;
5
6
  };
@@ -8,19 +9,38 @@ type MultiResult = {
8
9
  [browserName: string]: ImageCompareResult | number;
9
10
  };
10
11
  export type Result = MultiResult | (ImageCompareResult | number);
11
- export type NativeContextType = boolean | Record<string, boolean>;
12
- export type MultiremoteCommandResult = {
13
- command: string;
14
- method: string;
15
- endpoint: string;
16
- body: Record<string, any>;
17
- result: {
18
- value: string;
19
- };
20
- sessionId: string | undefined;
21
- cid: string;
22
- type: string;
12
+ export type MobileInstanceData = {
13
+ devicePixelRatio: number;
14
+ deviceRectangles: DeviceRectangles;
23
15
  };
16
+ export type getFolderMethodOptions = CheckElementMethodOptions | CheckFullPageMethodOptions | CheckScreenMethodOptions | SaveElementMethodOptions | SaveFullPageMethodOptions | SaveScreenMethodOptions;
17
+ export type GetInstanceDataOptions = {
18
+ currentBrowser: WebdriverIO.Browser;
19
+ initialDeviceRectangles: DeviceRectangles;
20
+ isNativeContext: boolean;
21
+ };
22
+ export type EnrichTestContextOptions = {
23
+ commandName: string;
24
+ currentTestContext: TestContext;
25
+ instanceData: InstanceData;
26
+ tag: string;
27
+ };
28
+ export type GetMobileInstanceDataOptions = {
29
+ currentBrowser: WebdriverIO.Browser;
30
+ initialDeviceRectangles: DeviceRectangles;
31
+ isNativeContext: boolean;
32
+ nativeWebScreenshot: boolean;
33
+ };
34
+ export interface WrapWithContextOptions<T extends (...args: any[]) => any> {
35
+ browser: WebdriverIO.Browser;
36
+ command: T;
37
+ contextManager: ContextManager;
38
+ getArgs: () => Parameters<T>;
39
+ }
40
+ export interface WdioIcsOptions {
41
+ logName?: string;
42
+ name?: string;
43
+ }
24
44
  export interface WdioIcsCommonOptions {
25
45
  hideElements?: (WebdriverIO.Element | ChainablePromiseElement)[];
26
46
  removeElements?: (WebdriverIO.Element | ChainablePromiseElement)[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,YAAY,EACf,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAE1D,KAAK,WAAW,GAAG;IACf,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAAC;CAC3C,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACpD,KAAK,WAAW,GAAG;IACf,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,MAAM,CAAC;CACtD,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAAC;AACjE,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACjE,MAAM,MAAM,wBAAwB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACpB,CAAA;AAED,MAAM,WAAW,oBAAoB;IACjC,YAAY,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;IACjE,cAAc,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;CACtE;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAC9D,oBAAoB,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;CAC5E;AAED,MAAM,WAAW,8BACb,SAAQ,IAAI,CAAC,0BAA0B,EAAE,MAAM,oBAAoB,CAAC,EAChE,oBAAoB;CAAG;AAC/B,MAAM,WAAW,6BACb,SAAQ,IAAI,CAAC,yBAAyB,EAAE,MAAM,oBAAoB,CAAC,EAC/D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,4BACb,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,EAC9D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,2BACb,SAAQ,IAAI,CAAC,uBAAuB,EAAE,MAAM,oBAAoB,CAAC,EAC7D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,6BACb,SAAQ,IAAI,CAAC,yBAAyB,EAAE,MAAM,oBAAoB,CAAC,EAC/D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,4BACb,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,EAC9D,oBAAoB;CAAG;AAE/B,MAAM,WAAW,oBAAqB,SAAQ,YAAY;CAAG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,YAAY,EACf,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD,KAAK,WAAW,GAAG;IACf,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAAC;CAC3C,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACpD,KAAK,WAAW,GAAG;IACf,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,MAAM,CAAC;CACtD,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAAC;AACjE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,CAAA;AACD,MAAM,MAAM,sBAAsB,GAC5B,yBAAyB,GACzB,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,yBAAyB,GACzB,uBAAuB,CAAC;AAC9B,MAAM,MAAM,sBAAsB,GAAG;IACjC,cAAc,EAAE,WAAW,CAAC,OAAO,CAAC;IACpC,uBAAuB,EAAE,gBAAgB,CAAC;IAC1C,eAAe,EAAE,OAAO,CAAA;CAC3B,CAAA;AACD,MAAM,MAAM,wBAAwB,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,WAAW,CAAC;IAChC,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;CACf,CAAA;AACD,MAAM,MAAM,4BAA4B,GAAG;IACvC,cAAc,EAAE,WAAW,CAAC,OAAO,CAAC;IACpC,uBAAuB,EAAE,gBAAgB,CAAC;IAC1C,eAAe,EAAC,OAAO,CAAC;IACxB,mBAAmB,EAAC,OAAO,CAAC;CAC/B,CAAA;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG;IACrE,OAAO,EAAE,WAAW,CAAC,OAAO,CAAA;IAC5B,OAAO,EAAE,CAAC,CAAA;IACV,cAAc,EAAE,cAAc,CAAA;IAC9B,OAAO,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACjC,YAAY,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;IACjE,cAAc,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;CACtE;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAC9D,oBAAoB,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;CAC5E;AAED,MAAM,WAAW,8BACb,SAAQ,IAAI,CAAC,0BAA0B,EAAE,MAAM,oBAAoB,CAAC,EAChE,oBAAoB;CAAG;AAC/B,MAAM,WAAW,6BACb,SAAQ,IAAI,CAAC,yBAAyB,EAAE,MAAM,oBAAoB,CAAC,EAC/D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,4BACb,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,EAC9D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,2BACb,SAAQ,IAAI,CAAC,uBAAuB,EAAE,MAAM,oBAAoB,CAAC,EAC7D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,6BACb,SAAQ,IAAI,CAAC,yBAAyB,EAAE,MAAM,oBAAoB,CAAC,EAC/D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,4BACb,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,EAC9D,oBAAoB;CAAG;AAE/B,MAAM,WAAW,oBAAqB,SAAQ,YAAY;CAAG"}
package/dist/utils.d.ts CHANGED
@@ -1,17 +1,16 @@
1
- import type { Folders, InstanceData, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, TestContext } from 'webdriver-image-comparison';
2
- import type { NativeContextType } from './types.js';
1
+ import type { Folders, InstanceData, TestContext } from 'webdriver-image-comparison';
2
+ import type { EnrichTestContextOptions, getFolderMethodOptions, GetInstanceDataOptions } from './types.js';
3
3
  /**
4
4
  * Get the folders data
5
5
  *
6
6
  * If folder options are passed in use those values
7
7
  * Otherwise, use the values set during instantiation
8
8
  */
9
- type getFolderMethodOptions = CheckElementMethodOptions | CheckFullPageMethodOptions | CheckScreenMethodOptions | SaveElementMethodOptions | SaveFullPageMethodOptions | SaveScreenMethodOptions;
10
9
  export declare function getFolders(methodOptions: getFolderMethodOptions, folders: Folders, currentTestPath: string): Folders;
11
10
  /**
12
- * Get the size of a screenshot in pixels without the device pixel ratio
11
+ * Get the size of a base64 screenshot in pixels without the device pixel ratio
13
12
  */
14
- export declare function getScreenshotSize(screenshot: string, devicePixelRation?: number): {
13
+ export declare function getBase64ScreenshotSize(screenshot: string, devicePixelRation?: number): {
15
14
  height: number;
16
15
  width: number;
17
16
  };
@@ -29,27 +28,21 @@ export declare function getLtOptions(capabilities: WebdriverIO.Capabilities): an
29
28
  /**
30
29
  * Get the instance data
31
30
  */
32
- export declare function getInstanceData(currentBrowser: WebdriverIO.Browser): Promise<InstanceData>;
31
+ export declare function getInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext }: GetInstanceDataOptions): Promise<InstanceData>;
33
32
  /**
34
33
  * Traverse up the scope chain until browser element was reached
35
34
  */
36
35
  export declare function getBrowserObject(elem: WebdriverIO.Element | WebdriverIO.Browser): WebdriverIO.Browser;
36
+ export declare function getNativeContext({ capabilities, isMobile }: {
37
+ capabilities: WebdriverIO.Capabilities;
38
+ isMobile: boolean;
39
+ }): boolean;
37
40
  /**
38
- * We can't say it's native context if the autoWebview is provided and set to true, for all other cases we can say it's native
39
- */
40
- export declare function determineNativeContext(driver: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): NativeContextType;
41
- /**
42
- * Get the native context for the current browser
41
+ * Make sure we have all the data for the test context
43
42
  */
44
- export declare function getNativeContext(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, currentBrowser: WebdriverIO.Browser, nativeContext: NativeContextType): boolean;
43
+ export declare function enrichTestContext({ commandName, currentTestContext: { framework, parent, title, }, instanceData: { appName, browserName, browserVersion, deviceName, isAndroid, isIOS, isMobile, platformName, platformVersion, }, tag, }: EnrichTestContextOptions): TestContext;
45
44
  /**
46
- * Make sure we have all the data for the test context
45
+ * Check if the current browser supports isBidi screenshots
47
46
  */
48
- export declare function enrichTestContext({ commandName, currentTestContext: { framework, parent, title, }, instanceData: { appName, browserName, browserVersion, deviceName, isAndroid, isIOS, isMobile, platformName, platformVersion, }, tag, }: {
49
- commandName: string;
50
- currentTestContext: TestContext;
51
- instanceData: InstanceData;
52
- tag: string;
53
- }): TestContext;
54
- export {};
47
+ export declare function isBiDiScreenshotSupported(driver: WebdriverIO.Browser): boolean;
55
48
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,OAAO,EACP,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,WAAW,EACd,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAOnD;;;;;GAKG;AACH,KAAK,sBAAsB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,yBAAyB,GACzB,uBAAuB,CAAC;AAE9B,wBAAgB,UAAU,CACtB,aAAa,EAAE,sBAAsB,EACrC,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,MAAM,GACxB,OAAO,CAMT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,iBAAiB,SAAI,GAAG;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB,CAKA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE;IAAC,MAAM,EAAC,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAAG,MAAM,CAOhH;AAkFD;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,YAAY,GAAG,GAAG,GAAG,SAAS,CAMpF;AAmCD;;GAEG;AACH,wBAAsB,eAAe,CAAC,cAAc,EAAE,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA+DhG;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAE,IAAI,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,CAGtG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB,GAC7D,iBAAiB,CAmDnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB,EAC7D,cAAc,EAAE,WAAW,CAAC,OAAO,EACnC,aAAa,EAAE,iBAAiB,GACjC,OAAO,CAQT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,EACI,WAAW,EACX,kBAAkB,EAAE,EAChB,SAAS,EACT,MAAM,EACN,KAAK,GACR,EACD,YAAY,EAAE,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,eAAe,GAClB,EACD,GAAG,GACN,EACD;IACI,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,WAAW,CAAC;IAChC,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;CACf,GACF,WAAW,CAuBb"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACpF,OAAO,KAAK,EACR,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EAIzB,MAAM,YAAY,CAAA;AAEnB;;;;;GAKG;AAEH,wBAAgB,UAAU,CACtB,aAAa,EAAE,sBAAsB,EACrC,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,MAAM,GACxB,OAAO,CAMT;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,iBAAiB,SAAI,GAAG;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB,CAKA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE;IAAC,MAAM,EAAC,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAAG,MAAM,CAOhH;AAiGD;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,YAAY,GAAG,GAAG,GAAG,SAAS,CAMpF;AAmCD;;GAEG;AACH,wBAAsB,eAAe,CAAC,EAClC,cAAc,EACd,uBAAuB,EACvB,eAAe,EAClB,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAgEhD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAE,IAAI,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,CAGtG;AAOD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,EACvD;IAAE,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAC9D,OAAO,CAuBT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,EACI,WAAW,EACX,kBAAkB,EAAE,EAChB,SAAS,EACT,MAAM,EACN,KAAK,GACR,EACD,YAAY,EAAE,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,eAAe,GAClB,EACD,GAAG,GACN,EAAE,wBAAwB,GAAG,WAAW,CAuB5C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,GAAG,OAAO,CAK9E"}
package/dist/utils.js CHANGED
@@ -1,5 +1,10 @@
1
- import { IOS_OFFSETS } from 'webdriver-image-comparison';
2
- import { NOT_KNOWN } from 'webdriver-image-comparison/dist/helpers/constants.js';
1
+ import { getMobileScreenSize, getMobileViewPortPosition, IOS_OFFSETS, NOT_KNOWN } from 'webdriver-image-comparison';
2
+ /**
3
+ * Get the folders data
4
+ *
5
+ * If folder options are passed in use those values
6
+ * Otherwise, use the values set during instantiation
7
+ */
3
8
  export function getFolders(methodOptions, folders, currentTestPath) {
4
9
  return {
5
10
  actualFolder: methodOptions.actualFolder ?? folders.actualFolder,
@@ -8,9 +13,9 @@ export function getFolders(methodOptions, folders, currentTestPath) {
8
13
  };
9
14
  }
10
15
  /**
11
- * Get the size of a screenshot in pixels without the device pixel ratio
16
+ * Get the size of a base64 screenshot in pixels without the device pixel ratio
12
17
  */
13
- export function getScreenshotSize(screenshot, devicePixelRation = 1) {
18
+ export function getBase64ScreenshotSize(screenshot, devicePixelRation = 1) {
14
19
  return {
15
20
  height: Buffer.from(screenshot, 'base64').readUInt32BE(20) / devicePixelRation,
16
21
  width: Buffer.from(screenshot, 'base64').readUInt32BE(16) / devicePixelRation,
@@ -20,7 +25,7 @@ export function getScreenshotSize(screenshot, devicePixelRation = 1) {
20
25
  * Get the device pixel ratio
21
26
  */
22
27
  export function getDevicePixelRatio(screenshot, deviceScreenSize) {
23
- const screenshotSize = getScreenshotSize(screenshot);
28
+ const screenshotSize = getBase64ScreenshotSize(screenshot);
24
29
  const devicePixelRatio = Math.round(screenshotSize.width / deviceScreenSize.width) === Math.round(screenshotSize.height / deviceScreenSize.height)
25
30
  ? Math.round(screenshotSize.width / deviceScreenSize.width)
26
31
  : Math.round(screenshotSize.height / deviceScreenSize.width);
@@ -29,21 +34,42 @@ export function getDevicePixelRatio(screenshot, deviceScreenSize) {
29
34
  /**
30
35
  * Get the mobile instance data
31
36
  */
32
- async function getMobileInstanceData({ currentBrowser, isAndroid, isMobile }) {
33
- const deviceScreenSize = {
34
- height: 0,
35
- width: 0,
36
- };
37
- const devicePlatformRect = {
38
- statusBar: { height: 0, x: 0, width: 0, y: 0 },
39
- homeBar: { height: 0, x: 0, width: 0, y: 0 },
40
- };
37
+ async function getMobileInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext, nativeWebScreenshot, }) {
38
+ const { isAndroid, isIOS, isMobile } = currentBrowser;
41
39
  let devicePixelRatio = 1;
40
+ let deviceRectangles = initialDeviceRectangles;
42
41
  if (isMobile) {
42
+ const executor = (fn, ...args) => currentBrowser.execute(fn, ...args);
43
+ const getUrl = () => currentBrowser.getUrl();
44
+ const url = (arg) => currentBrowser.url(arg);
43
45
  const currentDriverCapabilities = currentBrowser.capabilities;
44
- const { height, width } = await currentBrowser.getWindowSize();
45
- deviceScreenSize.height = height;
46
- deviceScreenSize.width = width;
46
+ const { height: screenHeight, width: screenWidth } = await getMobileScreenSize({
47
+ currentBrowser,
48
+ executor,
49
+ isIOS,
50
+ isNativeContext,
51
+ });
52
+ // Update the width for the device rectangles for bottomBar, screenSize, statusBar, statusBarAndAddressBar
53
+ deviceRectangles.screenSize.height = screenHeight;
54
+ deviceRectangles.screenSize.width = screenWidth;
55
+ deviceRectangles.bottomBar.width = screenWidth;
56
+ deviceRectangles.statusBarAndAddressBar.width = screenWidth;
57
+ deviceRectangles.statusBar.width = screenWidth;
58
+ deviceRectangles = await getMobileViewPortPosition({
59
+ initialDeviceRectangles,
60
+ isAndroid,
61
+ isIOS,
62
+ isNativeContext,
63
+ methods: {
64
+ executor,
65
+ getUrl,
66
+ url,
67
+ },
68
+ nativeWebScreenshot,
69
+ screenHeight,
70
+ screenWidth,
71
+ });
72
+ // @TODO: 20250317: When we have all things tested with the above, we can simplify the below part to only use the iOS part
47
73
  // @TODO: This is al based on PORTRAIT mode
48
74
  if (isAndroid && currentDriverCapabilities) {
49
75
  // We use a few `@ts-ignore` here because `pixelRatio` and `statBarHeight`
@@ -56,35 +82,38 @@ async function getMobileInstanceData({ currentBrowser, isAndroid, isMobile }) {
56
82
  // @ts-ignore
57
83
  if (currentDriverCapabilities?.statBarHeight !== undefined) {
58
84
  // @ts-ignore
59
- devicePlatformRect.statusBar.height = currentDriverCapabilities?.statBarHeight;
60
- devicePlatformRect.statusBar.width = width;
85
+ deviceRectangles.statusBar.height = currentDriverCapabilities?.statBarHeight;
86
+ deviceRectangles.statusBar.width = deviceRectangles.screenSize.width;
61
87
  }
62
88
  }
63
89
  else {
64
90
  // This is to already determine the device pixel ratio if it's not set in the capabilities
65
91
  const base64Image = await currentBrowser.takeScreenshot();
66
- devicePixelRatio = getDevicePixelRatio(base64Image, deviceScreenSize);
67
- const isIphone = width < 1024 && height < 1024;
92
+ devicePixelRatio = getDevicePixelRatio(base64Image, deviceRectangles.screenSize);
93
+ const isIphone = deviceRectangles.screenSize.width < 1024 && deviceRectangles.screenSize.height < 1024;
68
94
  const deviceType = isIphone ? 'IPHONE' : 'IPAD';
69
95
  const defaultPortraitHeight = isIphone ? 667 : 1024;
70
- const portraitHeight = width > height ? width : height;
71
- const offsetPortraitHeight = Object.keys(IOS_OFFSETS[deviceType]).indexOf(portraitHeight.toString()) > -1 ? portraitHeight : defaultPortraitHeight;
96
+ const portraitHeight = deviceRectangles.screenSize.width > deviceRectangles.screenSize.height ?
97
+ deviceRectangles.screenSize.width :
98
+ deviceRectangles.screenSize.height;
99
+ const offsetPortraitHeight = Object.keys(IOS_OFFSETS[deviceType]).indexOf(portraitHeight.toString()) > -1 ?
100
+ portraitHeight :
101
+ defaultPortraitHeight;
72
102
  const currentOffsets = IOS_OFFSETS[deviceType][offsetPortraitHeight].PORTRAIT;
73
103
  // NOTE: The values for iOS are based on CSS pixels, so we need to multiply them with the devicePixelRatio,
74
104
  // This will NOT be done here but in a central place
75
- devicePlatformRect.statusBar = {
76
- y: 0,
105
+ deviceRectangles.statusBar = {
77
106
  x: 0,
78
- width,
107
+ y: 0,
108
+ width: deviceRectangles.screenSize.width,
79
109
  height: currentOffsets.STATUS_BAR,
80
110
  };
81
- devicePlatformRect.homeBar = currentOffsets.HOME_BAR;
111
+ deviceRectangles.homeBar = currentOffsets.HOME_BAR;
82
112
  }
83
113
  }
84
114
  return {
85
115
  devicePixelRatio,
86
- devicePlatformRect,
87
- deviceScreenSize,
116
+ deviceRectangles,
88
117
  };
89
118
  }
90
119
  /**
@@ -125,8 +154,7 @@ function getDeviceName(currentBrowser) {
125
154
  /**
126
155
  * Get the instance data
127
156
  */
128
- export async function getInstanceData(currentBrowser) {
129
- const NOT_KNOWN = 'not-known';
157
+ export async function getInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext }) {
130
158
  const { capabilities: currentCapabilities, requestedCapabilities } = currentBrowser;
131
159
  const { browserName: rawBrowserName = NOT_KNOWN, browserVersion: rawBrowserVersion = NOT_KNOWN, platformName: rawPlatformName = NOT_KNOWN, } = currentCapabilities;
132
160
  // Generic data
@@ -158,7 +186,7 @@ export async function getInstanceData(currentBrowser) {
158
186
  // 20241216: LT doesn't have the option to take a ChromeDriver screenshot, so if it's Android it's always native
159
187
  const nativeWebScreenshot = isAndroid && ltOptions || !!(requestedCapabilities['appium:nativeWebScreenshot']);
160
188
  const platformVersion = (rawPlatformVersion === undefined || rawPlatformVersion === '') ? NOT_KNOWN : rawPlatformVersion.toLowerCase();
161
- const { devicePixelRatio: mobileDevicePixelRatio, devicePlatformRect, deviceScreenSize, } = await getMobileInstanceData({ currentBrowser, isAndroid, isMobile });
189
+ const { devicePixelRatio: mobileDevicePixelRatio, deviceRectangles, } = await getMobileInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext, nativeWebScreenshot });
162
190
  devicePixelRatio = isMobile ? mobileDevicePixelRatio : devicePixelRatio;
163
191
  return {
164
192
  appName,
@@ -166,8 +194,7 @@ export async function getInstanceData(currentBrowser) {
166
194
  browserVersion,
167
195
  deviceName,
168
196
  devicePixelRatio,
169
- devicePlatformRect,
170
- deviceScreenSize,
197
+ deviceRectangles,
171
198
  isAndroid,
172
199
  isIOS,
173
200
  isMobile,
@@ -185,65 +212,28 @@ export function getBrowserObject(elem) {
185
212
  const elemObject = elem;
186
213
  return elemObject.parent ? getBrowserObject(elemObject.parent) : elem;
187
214
  }
188
- /**
189
- * We can't say it's native context if the autoWebview is provided and set to true, for all other cases we can say it's native
190
- */
191
- export function determineNativeContext(driver) {
192
- // First check if it's multi remote
193
- if (driver.isMultiremote) {
194
- return Object.keys(driver).reduce((acc, instanceName) => {
195
- const instance = driver[instanceName];
196
- if (instance.sessionId) {
197
- acc[instance.sessionId] = determineNativeContext(instance);
198
- }
199
- return acc;
200
- }, {});
201
- }
202
- // If not check if it's a mobile
203
- if (driver.isMobile) {
204
- const isAppiumAppCapPresent = (capabilities) => {
205
- const appiumKeys = [
206
- 'appium:app',
207
- 'appium:bundleId',
208
- 'appium:appPackage',
209
- 'appium:appActivity',
210
- 'appium:appWaitActivity',
211
- 'appium:appWaitPackage',
212
- 'appium:autoWebview',
213
- ];
214
- const optionsKeys = appiumKeys.map(key => key.replace('appium:', ''));
215
- const isInRoot = appiumKeys.some(key => capabilities[key] !== undefined);
216
- // @ts-expect-error
217
- const isInAppiumOptions = capabilities['appium:options'] &&
218
- // @ts-expect-error
219
- optionsKeys.some(key => capabilities['appium:options']?.[key] !== undefined);
220
- // @ts-expect-error
221
- const isInLtOptions = capabilities['lt:options'] &&
222
- // @ts-expect-error
223
- optionsKeys.some(key => capabilities['lt:options']?.[key] !== undefined);
224
- return !!(isInRoot || isInAppiumOptions || isInLtOptions);
225
- };
226
- const capabilities = driver.requestedCapabilities;
227
- const isBrowserNameFalse = !!capabilities.browserName === false;
228
- const isAutoWebviewFalse = !(capabilities['appium:autoWebview'] === true ||
229
- capabilities['appium:options']?.autoWebview === true ||
230
- capabilities['lt:options']?.autoWebview === true);
231
- return isBrowserNameFalse && isAppiumAppCapPresent(capabilities) && isAutoWebviewFalse;
232
- }
233
- // If not, it's webcontext
234
- return false;
235
- }
236
215
  /**
237
216
  * Get the native context for the current browser
238
217
  */
239
- export function getNativeContext(browser, currentBrowser, nativeContext) {
240
- if (browser.isMultiremote) {
241
- return nativeContext[currentBrowser.sessionId];
218
+ const appiumKeys = ['app', 'bundleId', 'appPackage', 'appActivity', 'appWaitActivity', 'appWaitPackage'];
219
+ export function getNativeContext({ capabilities, isMobile }) {
220
+ if (!capabilities || typeof capabilities !== 'object' || !isMobile) {
221
+ return false;
242
222
  }
243
- else if (typeof nativeContext === 'boolean') {
244
- return nativeContext;
245
- }
246
- return false;
223
+ const isAppiumAppCapPresent = (capabilities) => {
224
+ return appiumKeys.some((key) => (capabilities[key] !== undefined ||
225
+ capabilities[`appium:${key}`] !== undefined ||
226
+ capabilities['appium:options']?.[key] !== undefined ||
227
+ capabilities['lt:options']?.[key] !== undefined));
228
+ };
229
+ const isBrowserNameFalse = !!capabilities?.browserName === false;
230
+ const isAutoWebviewFalse = !(
231
+ // @ts-expect-error
232
+ capabilities?.autoWebview === true ||
233
+ capabilities['appium:autoWebview'] === true ||
234
+ capabilities['appium:options']?.autoWebview === true ||
235
+ capabilities['lt:options']?.autoWebview === true);
236
+ return isBrowserNameFalse && isAppiumAppCapPresent(capabilities) && isAutoWebviewFalse;
247
237
  }
248
238
  /**
249
239
  * Make sure we have all the data for the test context
@@ -272,3 +262,11 @@ export function enrichTestContext({ commandName, currentTestContext: { framework
272
262
  title,
273
263
  };
274
264
  }
265
+ /**
266
+ * Check if the current browser supports isBidi screenshots
267
+ */
268
+ export function isBiDiScreenshotSupported(driver) {
269
+ const { isBidi } = driver;
270
+ const isBiDiSupported = typeof driver.browsingContextCaptureScreenshot === 'function';
271
+ return isBidi && isBiDiSupported;
272
+ }
@@ -0,0 +1,8 @@
1
+ import type { WrapWithContextOptions } from './types.js';
2
+ /**
3
+ * Wrap the command with the context manager
4
+ * This will make sure that the context manager is updated when needed
5
+ * and that the command is executed in the correct context
6
+ */
7
+ export declare function wrapWithContext<T extends (...args: any[]) => any>(opts: WrapWithContextOptions<T>): () => Promise<ReturnType<T>>;
8
+ //# sourceMappingURL=wrapWithContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapWithContext.d.ts","sourceRoot":"","sources":["../src/wrapWithContext.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAGxD;;;;GAIG;AAEH,wBAAgB,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAkBhI"}
@@ -0,0 +1,21 @@
1
+ import { getInstanceData } from './utils.js';
2
+ /**
3
+ * Wrap the command with the context manager
4
+ * This will make sure that the context manager is updated when needed
5
+ * and that the command is executed in the correct context
6
+ */
7
+ export function wrapWithContext(opts) {
8
+ const { browser, command, contextManager, getArgs } = opts;
9
+ return async function () {
10
+ if (contextManager.needsUpdate) {
11
+ const instanceData = await getInstanceData({
12
+ currentBrowser: browser,
13
+ initialDeviceRectangles: contextManager.getViewportContext(),
14
+ isNativeContext: contextManager.isNativeContext,
15
+ });
16
+ contextManager.setViewPortContext(instanceData.deviceRectangles);
17
+ }
18
+ const finalArgs = getArgs();
19
+ return command.apply(this, finalArgs);
20
+ };
21
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@wdio/visual-service",
3
3
  "author": "Wim Selles - wswebcreation",
4
4
  "description": "Image comparison / visual regression testing for WebdriverIO",
5
- "version": "6.4.0",
5
+ "version": "8.0.0",
6
6
  "license": "MIT",
7
7
  "homepage": "https://webdriver.io/docs/visual-testing",
8
8
  "repository": {
@@ -20,11 +20,11 @@
20
20
  "type": "module",
21
21
  "types": "./dist/index.d.ts",
22
22
  "dependencies": {
23
- "@wdio/globals": "^9.9.1",
23
+ "@wdio/globals": "^9.12.7",
24
24
  "@wdio/logger": "^9.4.4",
25
- "@wdio/types": "^9.12.2",
25
+ "@wdio/types": "^9.12.6",
26
26
  "expect-webdriverio": "^5.1.0",
27
- "webdriver-image-comparison": "7.4.0"
27
+ "webdriver-image-comparison": "9.0.0"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "run-s clean build:*",