@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 +223 -0
- package/dist/contextManager.d.ts +15 -0
- package/dist/contextManager.d.ts.map +1 -0
- package/dist/contextManager.js +71 -0
- package/dist/service.d.ts +4 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +213 -148
- package/dist/types.d.ts +33 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +13 -20
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +87 -89
- package/dist/wrapWithContext.d.ts +8 -0
- package/dist/wrapWithContext.d.ts.map +1 -0
- package/dist/wrapWithContext.js +21 -0
- package/package.json +4 -4
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
|
+
πΈ 
|
|
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
|
|
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
|
-
|
|
17
|
+
get contextManager(): ContextManager;
|
|
16
18
|
}
|
|
17
19
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -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;
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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,
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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,
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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)[];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
2
|
-
import type {
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
45
|
+
* Check if the current browser supports isBidi screenshots
|
|
47
46
|
*/
|
|
48
|
-
export declare function
|
|
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
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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,
|
|
33
|
-
const
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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,
|
|
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 ?
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
y: 0,
|
|
105
|
+
deviceRectangles.statusBar = {
|
|
77
106
|
x: 0,
|
|
78
|
-
|
|
107
|
+
y: 0,
|
|
108
|
+
width: deviceRectangles.screenSize.width,
|
|
79
109
|
height: currentOffsets.STATUS_BAR,
|
|
80
110
|
};
|
|
81
|
-
|
|
111
|
+
deviceRectangles.homeBar = currentOffsets.HOME_BAR;
|
|
82
112
|
}
|
|
83
113
|
}
|
|
84
114
|
return {
|
|
85
115
|
devicePixelRatio,
|
|
86
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
return
|
|
245
|
-
|
|
246
|
-
|
|
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": "
|
|
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.
|
|
23
|
+
"@wdio/globals": "^9.12.7",
|
|
24
24
|
"@wdio/logger": "^9.4.4",
|
|
25
|
-
"@wdio/types": "^9.12.
|
|
25
|
+
"@wdio/types": "^9.12.6",
|
|
26
26
|
"expect-webdriverio": "^5.1.0",
|
|
27
|
-
"webdriver-image-comparison": "
|
|
27
|
+
"webdriver-image-comparison": "9.0.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "run-s clean build:*",
|