@wdio/visual-service 5.0.1 → 5.1.1
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 +180 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/matcher.d.ts +0 -1
- package/dist/matcher.d.ts.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +136 -40
- package/dist/storybook/Types.d.ts +8 -2
- package/dist/storybook/Types.d.ts.map +1 -1
- package/dist/storybook/launcher.d.ts.map +1 -1
- package/dist/storybook/launcher.js +6 -0
- package/dist/storybook/utils.d.ts +5 -1
- package/dist/storybook/utils.d.ts.map +1 -1
- package/dist/storybook/utils.js +86 -58
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +6 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,185 @@
|
|
|
1
1
|
# @wdio/visual-service
|
|
2
2
|
|
|
3
|
+
## 5.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 26c1a17: This release contains better support for Multiremote tests on the instances and global level, including better Native App support. This is related to issue https://github.com/webdriverio/visual-testing/issues/418 which is fixed by this release
|
|
8
|
+
|
|
9
|
+
# 💅 Polish
|
|
10
|
+
|
|
11
|
+
- cab1219: Update dependencies
|
|
12
|
+
- 2583542 / 8b1f837: add new tests
|
|
13
|
+
- eec29e1 / e2b2d38: update images
|
|
14
|
+
|
|
15
|
+
# 🐛 Bug Fixes
|
|
16
|
+
|
|
17
|
+
- 29f6f82 / 1ba9817: fix issue https://github.com/webdriverio/visual-testing/issues/418
|
|
18
|
+
|
|
19
|
+
# Committers: 1
|
|
20
|
+
|
|
21
|
+
- Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [26c1a17]
|
|
24
|
+
- webdriver-image-comparison@6.0.2
|
|
25
|
+
|
|
26
|
+
## 5.1.0
|
|
27
|
+
|
|
28
|
+
### Minor Changes
|
|
29
|
+
|
|
30
|
+
- a0e29f2: Adding storybook interaction testing
|
|
31
|
+
|
|
32
|
+
### Storybook Interaction Testing
|
|
33
|
+
|
|
34
|
+
Storybook Interaction Testing allows you to interact with your component by creating custom scripts with WDIO commands to set a component into a certain state. For example, see the code snippet below:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { browser, expect } from "@wdio/globals";
|
|
38
|
+
|
|
39
|
+
describe("Storybook Interaction", () => {
|
|
40
|
+
it("should create screenshots for the logged in state when it logs out", async () => {
|
|
41
|
+
const componentId = "example-page--logged-in";
|
|
42
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
43
|
+
|
|
44
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
45
|
+
`${componentId}-logged-in-state`
|
|
46
|
+
);
|
|
47
|
+
await $("button=Log out").click();
|
|
48
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
49
|
+
`${componentId}-logged-out-state`
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should create screenshots for the logged out state when it logs in", async () => {
|
|
54
|
+
const componentId = "example-page--logged-out";
|
|
55
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
56
|
+
|
|
57
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
58
|
+
`${componentId}-logged-out-state`
|
|
59
|
+
);
|
|
60
|
+
await $("button=Log in").click();
|
|
61
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
62
|
+
`${componentId}-logged-in-state`
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Two tests on two different components are executed. Each test first sets a state and then takes a screenshot. You will also notice that a new custom command has been introduced, which can be found [here](#new-custom-command).
|
|
69
|
+
|
|
70
|
+
The above spec file can be saved in a folder and added to the command line with the following command:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npm run test.local.desktop.storybook.localhost -- --spec='tests/specs/storybook-interaction/*.ts'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The Storybook runner will first automatically scan your Storybook instance and then add your tests to the stories that need to be compared. If you don't want the components that you use for interaction testing to be compared twice, you can add a filter to remove the "default" stories from the scan by providing the [`--skipStories`](#--skipstories) filter. This would look like this:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npm run test.local.desktop.storybook.localhost -- --skipStories="/example-page.*/gm" --spec='tests/specs/storybook-interaction/*.ts'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### New Custom Command
|
|
83
|
+
|
|
84
|
+
A new custom command called `browser.waitForStorybookComponentToBeLoaded({ id: 'componentId' })` will be added to the `browser/driver`-object that will automatically load the component and wait for it to be done, so you don't need to use the `browser.url('url.com')` method. It can be used like this
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { browser, expect } from "@wdio/globals";
|
|
88
|
+
|
|
89
|
+
describe("Storybook Interaction", () => {
|
|
90
|
+
it("should create screenshots for the logged in state when it logs out", async () => {
|
|
91
|
+
const componentId = "example-page--logged-in";
|
|
92
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
93
|
+
|
|
94
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
95
|
+
`${componentId}-logged-in-state`
|
|
96
|
+
);
|
|
97
|
+
await $("button=Log out").click();
|
|
98
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
99
|
+
`${componentId}-logged-out-state`
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should create screenshots for the logged out state when it logs in", async () => {
|
|
104
|
+
const componentId = "example-page--logged-out";
|
|
105
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
106
|
+
|
|
107
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
108
|
+
`${componentId}-logged-out-state`
|
|
109
|
+
);
|
|
110
|
+
await $("button=Log in").click();
|
|
111
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
112
|
+
`${componentId}-logged-in-state`
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The options are:
|
|
119
|
+
|
|
120
|
+
#### `clipSelector`
|
|
121
|
+
|
|
122
|
+
- **Type:** `string`
|
|
123
|
+
- **Mandatory:** No
|
|
124
|
+
- **Default:** `#storybook-root > :first-child` for Storybook V7 and `#root > :first-child:not(script):not(style)` for Storybook V6
|
|
125
|
+
- **Example:**
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
129
|
+
clipSelector: "#your-selector",
|
|
130
|
+
id: "componentId",
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This is the selector that will be used:
|
|
135
|
+
|
|
136
|
+
- to select the element to take the screenshot of
|
|
137
|
+
- for the element to wait to be visible before a screenshot is taken
|
|
138
|
+
|
|
139
|
+
#### `id`
|
|
140
|
+
|
|
141
|
+
- **Type:** `string`
|
|
142
|
+
- **Mandatory:** yes
|
|
143
|
+
- **Example:**
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
await browser.waitForStorybookComponentToBeLoaded({ '#your-selector', id: 'componentId' })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use the `id` of the story that can be found in the URL of the story. For example, the `id` in this URL `http://localhost:6006/?path=/story/example-page--logged-out` is `example-page--logged-out`
|
|
150
|
+
|
|
151
|
+
#### `timeout`
|
|
152
|
+
|
|
153
|
+
- **Type:** `number`
|
|
154
|
+
- **Mandatory:** No
|
|
155
|
+
- **Default:** 1100 milliseconds
|
|
156
|
+
- **Example:**
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
160
|
+
id: "componentId",
|
|
161
|
+
timeout: 20000,
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The max timeout we want to wait for a component to be visible after loading on the page
|
|
166
|
+
|
|
167
|
+
#### `url`
|
|
168
|
+
|
|
169
|
+
- **Type:** `string`
|
|
170
|
+
- **Mandatory:** No
|
|
171
|
+
- **Default:** `http://127.0.0.1:6006`
|
|
172
|
+
- **Example:**
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
176
|
+
id: "componentId",
|
|
177
|
+
url: "https://your.url",
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The URL where your Storybook instance is hosted.
|
|
182
|
+
|
|
3
183
|
## 5.0.1
|
|
4
184
|
|
|
5
185
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/// <reference types="./expect-webdriverio.js" />
|
|
2
1
|
import type { WicElement } from 'webdriver-image-comparison';
|
|
3
2
|
import WdioImageComparisonService from './service.js';
|
|
4
3
|
import VisualLauncher from './storybook/launcher.js';
|
|
5
4
|
import type { Output, Result, WdioCheckFullPageMethodOptions, WdioSaveFullPageMethodOptions, WdioSaveElementMethodOptions, WdioSaveScreenMethodOptions, WdioCheckElementMethodOptions, WdioCheckScreenMethodOptions } from './types.js';
|
|
5
|
+
import type { WaitForStorybookComponentToBeLoaded } from './storybook/Types.js';
|
|
6
6
|
declare global {
|
|
7
7
|
namespace WebdriverIO {
|
|
8
8
|
interface Browser {
|
|
@@ -38,6 +38,10 @@ declare global {
|
|
|
38
38
|
* Compares an image of the complete screen with the tabbable lines and dots
|
|
39
39
|
*/
|
|
40
40
|
checkTabbablePage(tag: string, checkTabbableOptions?: WdioCheckFullPageMethodOptions): Promise<Result>;
|
|
41
|
+
/**
|
|
42
|
+
* Waits for a component to be loaded
|
|
43
|
+
*/
|
|
44
|
+
waitForStorybookComponentToBeLoaded(options: WaitForStorybookComponentToBeLoaded): Promise<void>;
|
|
41
45
|
}
|
|
42
46
|
interface Element {
|
|
43
47
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,0BAA0B,MAAM,cAAc,CAAA;AACrD,OAAO,cAAc,MAAM,yBAAyB,CAAA;AACpD,OAAO,KAAK,EACR,MAAM,EACN,MAAM,EACN,8BAA8B,EAC9B,6BAA6B,EAC7B,4BAA4B,EAC5B,2BAA2B,EAC3B,6BAA6B,EAC7B,4BAA4B,EAC/B,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,sBAAsB,CAAA;AAE/E,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,OAAO;YACb;;eAEG;YACH,WAAW,CACP,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,EACX,kBAAkB,CAAC,EAAE,4BAA4B,GAClD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,UAAU,CACN,GAAG,EAAE,MAAM,EACX,iBAAiB,CAAC,EAAE,2BAA2B,GAChD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,kBAAkB,CACd,GAAG,EAAE,MAAM,EACX,yBAAyB,CAAC,EAAE,6BAA6B,GAC1D,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,gBAAgB,CACZ,GAAG,EAAE,MAAM,EACX,mBAAmB,CAAC,EAAE,6BAA6B,GACpD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,YAAY,CACR,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,EACX,mBAAmB,CAAC,EAAE,6BAA6B,GACpD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,WAAW,CACP,GAAG,EAAE,MAAM,EACX,kBAAkB,CAAC,EAAE,4BAA4B,GAClD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,mBAAmB,CACf,GAAG,EAAE,MAAM,EACX,oBAAoB,CAAC,EAAE,8BAA8B,GACtD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,iBAAiB,CACb,GAAG,EAAE,MAAM,EACX,oBAAoB,CAAC,EAAE,8BAA8B,GACtD,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,mCAAmC,CAC/B,OAAO,EAAE,mCAAmC,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;SACpB;QACD,UAAU,OAAO;SAAG;QACpB,UAAU,YAAY;YAClB,kBAAkB,CAAC,EAAC;gBAChB,OAAO,CAAC,EAAE,MAAM,CAAC;aACpB,CAAA;SACJ;KACJ;IAED,UAAU,iBAAiB,CAAC;QAGxB,UAAU,QAAQ,CAAC,CAAC,EAAE,CAAC;YACnB;;;;;eAKG;YACH,qBAAqB,CACjB,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EAC1D,OAAO,CAAC,EAAE,4BAA4B,GACvC,CAAC,CAAA;YACJ,qBAAqB,CACjB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,4BAA4B,GACvC,CAAC,CAAA;YACJ;;;;;eAKG;YACH,uBAAuB,CACnB,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EAC1D,OAAO,CAAC,EAAE,8BAA8B,GACzC,CAAC,CAAA;YACJ,uBAAuB,CACnB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,8BAA8B,GACzC,CAAC,CAAA;YACJ;;;;;eAKG;YACH,sBAAsB,CAClB,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EAC1D,OAAO,CAAC,EAAE,6BAA6B,GACxC,CAAC,CAAA;YACJ,sBAAsB,CAClB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,6BAA6B,GACxC,CAAC,CAAA;YACJ;;;;;eAKG;YACH,2BAA2B,CACvB,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EAC1D,OAAO,CAAC,EAAE,8BAA8B,GACzC,CAAC,CAAA;YACJ,2BAA2B,CACvB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,8BAA8B,GACzC,CAAC,CAAA;SACP;KACJ;CACJ;AAED,eAAe,0BAA0B,CAAA;AACzC,eAAO,MAAM,QAAQ,uBAAiB,CAAA"}
|
package/dist/matcher.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="./expect-webdriverio.js" />
|
|
2
1
|
import type { WdioCheckFullPageMethodOptions, WdioCheckElementMethodOptions, WdioCheckScreenMethodOptions } from './types.js';
|
|
3
2
|
export declare function toMatchScreenSnapshot(browser: WebdriverIO.Browser, tag: string, expectedResultOrOptions?: number | ExpectWebdriverIO.PartialMatcher, optionsOrUndefined?: WdioCheckScreenMethodOptions): Promise<{
|
|
4
3
|
pass: boolean;
|
package/dist/matcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,8BAA8B,EAC9B,6BAA6B,EAC7B,4BAA4B,EAC/B,MAAM,YAAY,CAAA;AA2FnB,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,WAAW,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,uBAAuB,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EACnE,kBAAkB,CAAC,EAAE,4BAA4B;;;GAKpD;AAED,wBAAsB,uBAAuB,CACzC,OAAO,EAAE,WAAW,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,uBAAuB,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EACnE,kBAAkB,CAAC,EAAE,8BAA8B;;;GAKtD;AAED,wBAAsB,sBAAsB,CACxC,OAAO,EAAE,WAAW,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,uBAAuB,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EACnE,kBAAkB,CAAC,EAAE,6BAA6B;;;GAMrD;AAED,wBAAsB,2BAA2B,CAC7C,OAAO,EAAE,WAAW,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,uBAAuB,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,cAAc,EACnE,kBAAkB,CAAC,EAAE,8BAA8B;;;GAKtD"}
|
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,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EACH,SAAS,EAUZ,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,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EACH,SAAS,EAUZ,MAAM,4BAA4B,CAAA;AAwBnC,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,SAAS;;IAI7D,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,OAAO,CAAC,gBAAgB,CAA+B;gBAE3C,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM;IAM1F;;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;IAgCjE,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI;IAKhC,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;CAkQzF"}
|
package/dist/service.js
CHANGED
|
@@ -2,8 +2,9 @@ import logger from '@wdio/logger';
|
|
|
2
2
|
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, } from 'webdriver-image-comparison';
|
|
5
|
-
import { determineNativeContext, getFolders, getInstanceData } from './utils.js';
|
|
5
|
+
import { determineNativeContext, getFolders, getInstanceData, getNativeContext } from './utils.js';
|
|
6
6
|
import { toMatchScreenSnapshot, toMatchFullPageSnapshot, toMatchElementSnapshot, toMatchTabbablePageSnapshot } from './matcher.js';
|
|
7
|
+
import { waitForStorybookComponentToBeLoaded } from './storybook/utils.js';
|
|
7
8
|
const log = logger('@wdio/visual-service');
|
|
8
9
|
const elementCommands = { saveElement, checkElement };
|
|
9
10
|
const pageCommands = {
|
|
@@ -13,6 +14,7 @@ const pageCommands = {
|
|
|
13
14
|
checkScreen,
|
|
14
15
|
checkFullPageScreen,
|
|
15
16
|
checkTabbablePage,
|
|
17
|
+
waitForStorybookComponentToBeLoaded,
|
|
16
18
|
};
|
|
17
19
|
export default class WdioImageComparisonService extends BaseClass {
|
|
18
20
|
#config;
|
|
@@ -41,6 +43,9 @@ export default class WdioImageComparisonService extends BaseClass {
|
|
|
41
43
|
else {
|
|
42
44
|
await this.#extendMultiremoteBrowser(capabilities);
|
|
43
45
|
}
|
|
46
|
+
if (browser.isMultiremote) {
|
|
47
|
+
this.#setupMultiremoteContextListener();
|
|
48
|
+
}
|
|
44
49
|
/**
|
|
45
50
|
* add custom matcher for visual comparison when expect has been added.
|
|
46
51
|
* this is not the case in standalone mode
|
|
@@ -64,7 +69,8 @@ export default class WdioImageComparisonService extends BaseClass {
|
|
|
64
69
|
afterCommand(commandName, _args, result, error) {
|
|
65
70
|
// This is for the cases where in the E2E tests we switch to a WEBVIEW or back to NATIVE_APP context
|
|
66
71
|
if (commandName === 'getContext' && error === undefined && typeof result === 'string') {
|
|
67
|
-
|
|
72
|
+
// Multiremote logic is handled in the `before` method during an event listener
|
|
73
|
+
this._isNativeContext = this._browser?.isMultiremote ? this._isNativeContext : result.includes('NATIVE');
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
#getBaselineFolder() {
|
|
@@ -84,69 +90,159 @@ export default class WdioImageComparisonService extends BaseClass {
|
|
|
84
90
|
}
|
|
85
91
|
return baselineFolder;
|
|
86
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Add commands to the Multi Remote browser object
|
|
95
|
+
*/
|
|
87
96
|
async #extendMultiremoteBrowser(capabilities) {
|
|
88
97
|
const browser = this._browser;
|
|
89
98
|
const browserNames = Object.keys(capabilities);
|
|
90
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Add all the commands to each browser in the Multi Remote
|
|
101
|
+
*/
|
|
91
102
|
for (const browserName of browserNames) {
|
|
92
|
-
|
|
93
|
-
const browserInstance =
|
|
103
|
+
log.info(`Adding commands to Multi Browser: ${browserName}`);
|
|
104
|
+
const browserInstance = browser.getInstance(browserName);
|
|
94
105
|
await this.#addCommandsToBrowser(browserInstance);
|
|
95
106
|
}
|
|
96
107
|
/**
|
|
97
108
|
* Add all the commands to the global browser object that will execute
|
|
98
109
|
* on each browser in the Multi Remote
|
|
110
|
+
* Start with the page commands
|
|
99
111
|
*/
|
|
100
|
-
for (const command of
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* casting command to `checkScreen` to simplify type handling here
|
|
111
|
-
*/
|
|
112
|
-
returnData[browserName] = browserInstance[command].call(browserInstance, ...args);
|
|
113
|
-
}
|
|
114
|
-
return returnData;
|
|
115
|
-
});
|
|
112
|
+
for (const [commandName, command] of Object.entries(pageCommands)) {
|
|
113
|
+
this.#addMultiremoteCommand(browser, browserNames, commandName, command);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Add all the element commands to the global browser object that will execute
|
|
117
|
+
* on each browser in the Multi Remote
|
|
118
|
+
*/
|
|
119
|
+
for (const [commandName, command] of Object.entries(elementCommands)) {
|
|
120
|
+
this.#addMultiremoteElementCommand(browser, browserNames, commandName, command);
|
|
116
121
|
}
|
|
117
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Add commands to the "normal" browser object
|
|
125
|
+
*/
|
|
118
126
|
async #addCommandsToBrowser(currentBrowser) {
|
|
119
127
|
const instanceData = await getInstanceData(currentBrowser);
|
|
120
|
-
const
|
|
128
|
+
const isNativeContext = getNativeContext(this._browser, currentBrowser, this._isNativeContext);
|
|
121
129
|
for (const [commandName, command] of Object.entries(elementCommands)) {
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
this.#addElementCommand(currentBrowser, commandName, command, instanceData, isNativeContext);
|
|
131
|
+
}
|
|
132
|
+
for (const [commandName, command] of Object.entries(pageCommands)) {
|
|
133
|
+
this.#addPageCommand(currentBrowser, commandName, command, instanceData, isNativeContext);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Add new element commands to the browser object
|
|
138
|
+
*/
|
|
139
|
+
#addElementCommand(browser, commandName, command, instanceData, isNativeContext) {
|
|
140
|
+
log.info(`Adding element command "${commandName}" to browser object`);
|
|
141
|
+
const self = this;
|
|
142
|
+
browser.addCommand(commandName, function (element, tag, elementOptions = {}) {
|
|
143
|
+
return command({
|
|
144
|
+
executor: (script, ...varArgs) => {
|
|
145
|
+
return this.execute.bind(browser)(script, ...varArgs);
|
|
146
|
+
},
|
|
147
|
+
getElementRect: this.getElementRect.bind(browser),
|
|
148
|
+
screenShot: this.takeScreenshot.bind(browser),
|
|
149
|
+
takeElementScreenshot: this.takeElementScreenshot.bind(browser),
|
|
150
|
+
}, instanceData, getFolders(elementOptions, self.folders, self.#getBaselineFolder()), element, tag, {
|
|
151
|
+
wic: self.defaultOptions,
|
|
152
|
+
method: elementOptions,
|
|
153
|
+
}, isNativeContext);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Add new page commands to the browser object
|
|
158
|
+
*/
|
|
159
|
+
#addPageCommand(browser, commandName, command, instanceData, isNativeContext) {
|
|
160
|
+
log.info(`Adding browser command "${commandName}" to browser object`);
|
|
161
|
+
const self = this;
|
|
162
|
+
if (commandName === 'waitForStorybookComponentToBeLoaded') {
|
|
163
|
+
browser.addCommand(commandName, (options) => waitForStorybookComponentToBeLoaded(options));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
browser.addCommand(commandName, function (tag, pageOptions = {}) {
|
|
124
167
|
return command({
|
|
125
168
|
executor: (script, ...varArgs) => {
|
|
126
|
-
return this.execute.bind(
|
|
169
|
+
return this.execute.bind(browser)(script, ...varArgs);
|
|
127
170
|
},
|
|
128
|
-
getElementRect: this.getElementRect.bind(
|
|
129
|
-
screenShot: this.takeScreenshot.bind(
|
|
130
|
-
|
|
131
|
-
}, instanceData, getFolders(elementOptions, self.folders, self.#getBaselineFolder()), element, tag, {
|
|
171
|
+
getElementRect: this.getElementRect.bind(browser),
|
|
172
|
+
screenShot: this.takeScreenshot.bind(browser),
|
|
173
|
+
}, instanceData, getFolders(pageOptions, self.folders, self.#getBaselineFolder()), tag, {
|
|
132
174
|
wic: self.defaultOptions,
|
|
133
|
-
method:
|
|
134
|
-
},
|
|
175
|
+
method: pageOptions,
|
|
176
|
+
}, isNativeContext);
|
|
135
177
|
});
|
|
136
178
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
}
|
|
180
|
+
#addMultiremoteCommand(browser, browserNames, commandName, command) {
|
|
181
|
+
log.info(`Adding browser command "${commandName}" to Multi browser object`);
|
|
182
|
+
const self = this;
|
|
183
|
+
if (commandName === 'waitForStorybookComponentToBeLoaded') {
|
|
184
|
+
browser.addCommand(commandName, waitForStorybookComponentToBeLoaded);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
browser.addCommand(commandName, async function (tag, pageOptions = {}) {
|
|
188
|
+
const returnData = {};
|
|
189
|
+
for (const browserName of browserNames) {
|
|
190
|
+
const browserInstance = browser.getInstance(browserName);
|
|
191
|
+
const isNativeContext = getNativeContext(self._browser, browserInstance, self._isNativeContext);
|
|
192
|
+
const instanceData = await getInstanceData(browserInstance);
|
|
193
|
+
returnData[browserName] = await command({
|
|
194
|
+
executor: (script, ...varArgs) => {
|
|
195
|
+
return browserInstance.execute.bind(browserInstance)(script, ...varArgs);
|
|
196
|
+
},
|
|
197
|
+
getElementRect: browserInstance.getElementRect.bind(browserInstance),
|
|
198
|
+
screenShot: browserInstance.takeScreenshot.bind(browserInstance),
|
|
199
|
+
}, instanceData, getFolders(pageOptions, self.folders, self.#getBaselineFolder()), tag, {
|
|
200
|
+
wic: self.defaultOptions,
|
|
201
|
+
method: pageOptions,
|
|
202
|
+
}, isNativeContext);
|
|
203
|
+
}
|
|
204
|
+
return returnData;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
#addMultiremoteElementCommand(browser, browserNames, commandName, command) {
|
|
209
|
+
log.info(`Adding element command "${commandName}" to Multi browser object`);
|
|
210
|
+
const self = this;
|
|
211
|
+
browser.addCommand(commandName, async function (tag, element, pageOptions = {}) {
|
|
212
|
+
const returnData = {};
|
|
213
|
+
for (const browserName of browserNames) {
|
|
214
|
+
const browserInstance = browser.getInstance(browserName);
|
|
215
|
+
const isNativeContext = getNativeContext(self._browser, browserInstance, self._isNativeContext);
|
|
216
|
+
const instanceData = await getInstanceData(browserInstance);
|
|
217
|
+
returnData[browserName] = await command({
|
|
141
218
|
executor: (script, ...varArgs) => {
|
|
142
|
-
return
|
|
219
|
+
return browserInstance.execute.bind(browserInstance)(script, ...varArgs);
|
|
143
220
|
},
|
|
144
|
-
getElementRect:
|
|
145
|
-
screenShot:
|
|
146
|
-
|
|
221
|
+
getElementRect: browserInstance.getElementRect.bind(browserInstance),
|
|
222
|
+
screenShot: browserInstance.takeScreenshot.bind(browserInstance),
|
|
223
|
+
takeElementScreenshot: browserInstance.takeElementScreenshot.bind(browserInstance),
|
|
224
|
+
}, instanceData, getFolders(pageOptions, self.folders, self.#getBaselineFolder()), tag, element, {
|
|
147
225
|
wic: self.defaultOptions,
|
|
148
226
|
method: pageOptions,
|
|
149
|
-
},
|
|
227
|
+
}, isNativeContext);
|
|
228
|
+
}
|
|
229
|
+
return returnData;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
#setupMultiremoteContextListener() {
|
|
233
|
+
const multiremoteBrowser = this._browser;
|
|
234
|
+
const browserInstances = multiremoteBrowser.instances;
|
|
235
|
+
for (const instanceName of browserInstances) {
|
|
236
|
+
const instance = multiremoteBrowser[instanceName];
|
|
237
|
+
instance.on('result', (result) => {
|
|
238
|
+
if (result.command === 'getContext') {
|
|
239
|
+
const value = result.result.value;
|
|
240
|
+
const sessionId = instance.sessionId;
|
|
241
|
+
if (typeof this._isNativeContext !== 'object' || this._isNativeContext === null) {
|
|
242
|
+
this._isNativeContext = {};
|
|
243
|
+
}
|
|
244
|
+
this._isNativeContext[sessionId] = value.includes('NATIVE');
|
|
245
|
+
}
|
|
150
246
|
});
|
|
151
247
|
}
|
|
152
248
|
}
|
|
@@ -39,7 +39,7 @@ export type CreateTestFileOptions = {
|
|
|
39
39
|
framework: string;
|
|
40
40
|
numShards: number;
|
|
41
41
|
skipStories: string[] | RegExp;
|
|
42
|
-
storiesJson:
|
|
42
|
+
storiesJson: StorybookData[];
|
|
43
43
|
storybookUrl: string;
|
|
44
44
|
};
|
|
45
45
|
export interface CapabilityMap {
|
|
@@ -71,7 +71,7 @@ export type CategoryComponent = {
|
|
|
71
71
|
component: string;
|
|
72
72
|
};
|
|
73
73
|
export type ScanStorybookReturnData = {
|
|
74
|
-
storiesJson:
|
|
74
|
+
storiesJson: StorybookData[];
|
|
75
75
|
storybookUrl: string;
|
|
76
76
|
tempDir: string;
|
|
77
77
|
};
|
|
@@ -84,4 +84,10 @@ export type EmulatedDeviceType = {
|
|
|
84
84
|
};
|
|
85
85
|
userAgent: string;
|
|
86
86
|
};
|
|
87
|
+
export type WaitForStorybookComponentToBeLoaded = {
|
|
88
|
+
clipSelector?: string;
|
|
89
|
+
id: string;
|
|
90
|
+
timeout?: number;
|
|
91
|
+
url?: string;
|
|
92
|
+
};
|
|
87
93
|
//# sourceMappingURL=Types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Types.d.ts","sourceRoot":"","sources":["../../src/storybook/Types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAA;AACtF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAEzD,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;KACpB,CAAC;CACL;AAED,MAAM,WAAW,QAAQ;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,UAAU;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,MAAM,OAAO,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAAG;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAC,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC/B,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"Types.d.ts","sourceRoot":"","sources":["../../src/storybook/Types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/storybook/launcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AActD,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,SAAS;;gBAGrC,OAAO,EAAE,YAAY;IAK3B,SAAS,CAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,kBAAkB;
|
|
1
|
+
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/storybook/launcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AActD,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,SAAS;;gBAGrC,OAAO,EAAE,YAAY;IAK3B,SAAS,CAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,kBAAkB;IAuEpF,UAAU;CAiBnB"}
|
|
@@ -23,6 +23,8 @@ export default class VisualLauncher extends BaseClass {
|
|
|
23
23
|
const { storiesJson, storybookUrl, tempDir } = await scanStorybook(config, this.#options);
|
|
24
24
|
// Set an environment variable so it can be used in the onComplete hook
|
|
25
25
|
process.env.VISUAL_STORYBOOK_TEMP_SPEC_FOLDER = tempDir;
|
|
26
|
+
// Add the storybook URL to the environment variables
|
|
27
|
+
process.env.VISUAL_STORYBOOK_URL = storybookUrl;
|
|
26
28
|
// Check the capabilities
|
|
27
29
|
// Multiremote capabilities are not supported
|
|
28
30
|
if (typeof capabilities === 'object' && !Array.isArray(capabilities)) {
|
|
@@ -50,6 +52,8 @@ export default class VisualLauncher extends BaseClass {
|
|
|
50
52
|
const clipSelectorArgv = getArgvValue('--clipSelector', value => value);
|
|
51
53
|
// V6 has '#root' as the root element, V7 has '#storybook-root'
|
|
52
54
|
const clipSelector = (clipSelectorOption ?? clipSelectorArgv) ?? (version === 6 ? V6_CLIP_SELECTOR : CLIP_SELECTOR);
|
|
55
|
+
// Add the clip selector to the environment variables
|
|
56
|
+
process.env.VISUAL_STORYBOOK_CLIP_SELECTOR = clipSelector;
|
|
53
57
|
// --skipStories
|
|
54
58
|
const skipStoriesOption = this.#options?.storybook?.skipStories;
|
|
55
59
|
const skipStoriesArgv = getArgvValue('--skipStories', value => value);
|
|
@@ -84,6 +88,8 @@ export default class VisualLauncher extends BaseClass {
|
|
|
84
88
|
}
|
|
85
89
|
// Remove the environment variables
|
|
86
90
|
delete process.env.VISUAL_STORYBOOK_TEMP_SPEC_FOLDER;
|
|
91
|
+
delete process.env.VISUAL_STORYBOOK_URL;
|
|
92
|
+
delete process.env.VISUAL_STORYBOOK_CLIP_SELECTOR;
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Options } from '@wdio/types';
|
|
2
2
|
import type { ClassOptions } from 'webdriver-image-comparison';
|
|
3
|
-
import type { CategoryComponent, CreateItContent, CreateTestContent, CreateTestFileOptions, ScanStorybookReturnData, Stories, EmulatedDeviceType, CapabilityMap } from './Types.js';
|
|
3
|
+
import type { CategoryComponent, CreateItContent, CreateTestContent, CreateTestFileOptions, ScanStorybookReturnData, Stories, EmulatedDeviceType, CapabilityMap, WaitForStorybookComponentToBeLoaded } from './Types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Check if we run for Storybook
|
|
6
6
|
*/
|
|
@@ -50,6 +50,10 @@ export declare function writeTestFile(directoryPath: string, fileID: string, tes
|
|
|
50
50
|
* Create the test content
|
|
51
51
|
*/
|
|
52
52
|
export declare function createTestContent({ clip, clipSelector, folders, framework, skipStories, stories, storybookUrl }: CreateTestContent, itFunc?: typeof itFunction): string;
|
|
53
|
+
/**
|
|
54
|
+
* The custom command
|
|
55
|
+
*/
|
|
56
|
+
export declare function waitForStorybookComponentToBeLoaded(options: WaitForStorybookComponentToBeLoaded, isStorybookModeFunc?: typeof isStorybookMode): Promise<void>;
|
|
53
57
|
/**
|
|
54
58
|
* Create the file data
|
|
55
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/storybook/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAK1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EACR,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EAErB,uBAAuB,EACvB,OAAO,EAGP,kBAAkB,EAClB,aAAa,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/storybook/utils.ts"],"names":[],"mappings":"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"}
|
package/dist/storybook/utils.js
CHANGED
|
@@ -135,9 +135,11 @@ export function itFunction({ clip, clipSelector, folders: { baselineFolder }, fr
|
|
|
135
135
|
};
|
|
136
136
|
const it = `
|
|
137
137
|
${itText}(\`should take a${screenshotType} screenshot of ${id}\`, async () => {
|
|
138
|
-
await browser.
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
139
|
+
clipSelector: '${clipSelector}',
|
|
140
|
+
id: '${id}',
|
|
141
|
+
storybookUrl: '${storybookUrl}',
|
|
142
|
+
});
|
|
141
143
|
${clip
|
|
142
144
|
? `await expect($('${clipSelector}')).toMatchElementSnapshot('${id}-element', ${JSON.stringify(methodOptions)})`
|
|
143
145
|
: `await expect(browser).toMatchScreenSnapshot('${id}', ${JSON.stringify(methodOptions)})`}
|
|
@@ -168,53 +170,68 @@ itFunc = itFunction) {
|
|
|
168
170
|
const itFunctionOptions = { clip, clipSelector, folders, framework, skipStories, storybookUrl };
|
|
169
171
|
return stories.reduce((acc, storyData) => acc + itFunc({ ...itFunctionOptions, storyData }), '');
|
|
170
172
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
173
|
+
/**
|
|
174
|
+
* The custom command
|
|
175
|
+
*/
|
|
176
|
+
export async function waitForStorybookComponentToBeLoaded(options,
|
|
177
|
+
// For testing purposes only
|
|
178
|
+
isStorybookModeFunc = isStorybookMode) {
|
|
179
|
+
const isStorybook = isStorybookModeFunc();
|
|
180
|
+
if (isStorybook) {
|
|
181
|
+
const { clipSelector = process.env.VISUAL_STORYBOOK_CLIP_SELECTOR, id, url = process.env.VISUAL_STORYBOOK_URL, timeout = 11000, } = options;
|
|
182
|
+
await browser.url(`${url}iframe.html?id=${id}`);
|
|
183
|
+
await $(clipSelector).waitForDisplayed();
|
|
184
|
+
await browser.executeAsync(async (timeout, done) => {
|
|
185
|
+
let timedOut = false;
|
|
186
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
timedOut = true;
|
|
189
|
+
reject('Timeout: Not all images loaded within 11 seconds');
|
|
190
|
+
}, timeout);
|
|
191
|
+
});
|
|
192
|
+
const isImageLoaded = (img) => img.complete && img.naturalWidth > 0;
|
|
193
|
+
// Check for <img> elements
|
|
194
|
+
const imgElements = Array.from(document.querySelectorAll('img'));
|
|
195
|
+
const imgPromises = imgElements.map(img => isImageLoaded(img) ? Promise.resolve() : new Promise(resolve => {
|
|
196
|
+
img.onload = () => { if (!timedOut) {
|
|
197
|
+
resolve();
|
|
198
|
+
} };
|
|
199
|
+
img.onerror = () => { if (!timedOut) {
|
|
200
|
+
resolve();
|
|
201
|
+
} };
|
|
202
|
+
}));
|
|
203
|
+
// Check for CSS background images
|
|
204
|
+
const allElements = Array.from(document.querySelectorAll('*'));
|
|
205
|
+
const bgImagePromises = allElements.map(el => {
|
|
206
|
+
const bgImage = window.getComputedStyle(el).backgroundImage;
|
|
207
|
+
if (bgImage && bgImage !== 'none' && bgImage.startsWith('url')) {
|
|
208
|
+
const imageUrl = bgImage.slice(5, -2); // Extract URL from the 'url("")'
|
|
209
|
+
const image = new Image();
|
|
210
|
+
image.src = imageUrl;
|
|
211
|
+
return isImageLoaded(image) ? Promise.resolve() : new Promise(resolve => {
|
|
212
|
+
image.onload = () => { if (!timedOut) {
|
|
213
|
+
resolve();
|
|
214
|
+
} };
|
|
215
|
+
image.onerror = () => { if (!timedOut) {
|
|
216
|
+
resolve();
|
|
217
|
+
} };
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return Promise.resolve();
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
await Promise.race([Promise.all([...imgPromises, ...bgImagePromises]), timeoutPromise]);
|
|
224
|
+
done();
|
|
205
225
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
});
|
|
226
|
+
catch (error) {
|
|
227
|
+
done(error);
|
|
228
|
+
}
|
|
229
|
+
}, timeout);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
throw new Error('The method `waitForStorybookComponentToBeLoaded` can only be used in Storybook mode.');
|
|
233
|
+
}
|
|
216
234
|
}
|
|
217
|
-
`;
|
|
218
235
|
/**
|
|
219
236
|
* Create the file data
|
|
220
237
|
*/
|
|
@@ -223,33 +240,36 @@ export function createFileData(describeTitle, testContent) {
|
|
|
223
240
|
describe(\`${describeTitle}\`, () => {
|
|
224
241
|
${testContent}
|
|
225
242
|
});
|
|
226
|
-
${waitForAllImagesLoaded}
|
|
227
243
|
`;
|
|
228
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Filter the stories, by default only keep the stories, not the docs
|
|
247
|
+
*/
|
|
248
|
+
function filterStories(storiesJson) {
|
|
249
|
+
return Object.values(storiesJson)
|
|
250
|
+
// storyData?.type === 'story' is V7+, storyData.parameters?.docsOnly is V6
|
|
251
|
+
.filter((storyData) => storyData.type === 'story' || (storyData.parameters && !storyData.parameters.docsOnly));
|
|
252
|
+
}
|
|
229
253
|
/**
|
|
230
254
|
* Create the test files
|
|
231
255
|
*/
|
|
232
256
|
export function createTestFiles({ clip, clipSelector, directoryPath, folders, framework, numShards, skipStories, storiesJson, storybookUrl },
|
|
233
257
|
// For testing purposes only
|
|
234
258
|
createTestCont = createTestContent, createFileD = createFileData, writeTestF = writeTestFile) {
|
|
235
|
-
const storiesArray = Object.values(storiesJson)
|
|
236
|
-
// By default only keep the stories, not the docs
|
|
237
|
-
// storyData?.type === 'story' is V7+, storyData.parameters?.docsOnly is V6
|
|
238
|
-
.filter((storyData) => storyData.type === 'story' || (storyData.parameters && !storyData.parameters.docsOnly));
|
|
239
259
|
const fileNamePrefix = 'visual-storybook';
|
|
240
|
-
const createTestContentData = { clip, clipSelector, folders, framework, skipStories, stories:
|
|
260
|
+
const createTestContentData = { clip, clipSelector, folders, framework, skipStories, stories: storiesJson, storybookUrl };
|
|
241
261
|
if (numShards === 1) {
|
|
242
262
|
const testContent = createTestCont(createTestContentData);
|
|
243
263
|
const fileData = createFileD('All stories', testContent);
|
|
244
264
|
writeTestF(directoryPath, `${fileNamePrefix}-1-1`, fileData);
|
|
245
265
|
}
|
|
246
266
|
else {
|
|
247
|
-
const totalStories =
|
|
267
|
+
const totalStories = storiesJson.length;
|
|
248
268
|
const storiesPerShard = Math.ceil(totalStories / numShards);
|
|
249
269
|
for (let shard = 0; shard < numShards; shard++) {
|
|
250
270
|
const startIndex = shard * storiesPerShard;
|
|
251
271
|
const endIndex = Math.min(startIndex + storiesPerShard, totalStories);
|
|
252
|
-
const shardStories =
|
|
272
|
+
const shardStories = storiesJson.slice(startIndex, endIndex);
|
|
253
273
|
const testContent = createTestCont({ ...createTestContentData, stories: shardStories });
|
|
254
274
|
const fileId = `${fileNamePrefix}-${shard + 1}-${numShards}`;
|
|
255
275
|
const describeTitle = `Shard ${shard + 1} of ${numShards}`;
|
|
@@ -396,11 +416,19 @@ getArgvVal = getArgvValue, checkStorybookIsRun = checkStorybookIsRunning, saniti
|
|
|
396
416
|
const tempDir = resolve(tmpdir(), `wdio-storybook-tests-${Date.now()}`);
|
|
397
417
|
mkdirSync(tempDir);
|
|
398
418
|
log.info(`Using temporary folder for storybook specs: ${tempDir}`);
|
|
399
|
-
config.specs = [join(tempDir, '*.js')];
|
|
400
419
|
// Get the stories
|
|
401
420
|
const storiesJson = await getStoriesJsonFunc(storybookUrl);
|
|
421
|
+
const filteredStories = filterStories(storiesJson);
|
|
422
|
+
// Check if the specs are provided via the CLI so they can be added to the specs
|
|
423
|
+
// when users provide stories with interactive components
|
|
424
|
+
const isCliSpecs = getArgvVal('--spec', value => value);
|
|
425
|
+
const cliSpecs = [];
|
|
426
|
+
if (isCliSpecs) {
|
|
427
|
+
cliSpecs.push(...config.specs);
|
|
428
|
+
}
|
|
429
|
+
config.specs = [join(tempDir, '*.{js,mjs,ts}'), ...cliSpecs];
|
|
402
430
|
return {
|
|
403
|
-
storiesJson,
|
|
431
|
+
storiesJson: filteredStories,
|
|
404
432
|
storybookUrl,
|
|
405
433
|
tempDir,
|
|
406
434
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ type MultiResult = {
|
|
|
7
7
|
[browserName: string]: ImageCompareResult | number;
|
|
8
8
|
};
|
|
9
9
|
export type Result = MultiResult | (ImageCompareResult | number);
|
|
10
|
+
export type NativeContextType = boolean | Record<string, boolean>;
|
|
11
|
+
export type MultiremoteCommandResult = {
|
|
12
|
+
command: string;
|
|
13
|
+
method: string;
|
|
14
|
+
endpoint: string;
|
|
15
|
+
body: Record<string, any>;
|
|
16
|
+
result: {
|
|
17
|
+
value: string;
|
|
18
|
+
};
|
|
19
|
+
sessionId: string | undefined;
|
|
20
|
+
cid: string;
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
10
23
|
export interface WdioIcsCommonOptions {
|
|
11
24
|
hideElements?: WebdriverIO.Element[];
|
|
12
25
|
removeElements?: WebdriverIO.Element[];
|
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;AAEnC,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;
|
|
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;AAEnC,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,WAAW,CAAC,OAAO,EAAE,CAAC;IACrC,cAAc,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAC9D,oBAAoB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;CAChD;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;CAAI"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Folders, InstanceData, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions } from 'webdriver-image-comparison';
|
|
2
|
+
import type { NativeContextType } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Get the folders data
|
|
4
5
|
*
|
|
@@ -32,6 +33,10 @@ export declare function getBrowserObject(elem: WebdriverIO.Element | WebdriverIO
|
|
|
32
33
|
/**
|
|
33
34
|
* 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
|
|
34
35
|
*/
|
|
35
|
-
export declare function determineNativeContext(driver: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser):
|
|
36
|
+
export declare function determineNativeContext(driver: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): NativeContextType;
|
|
37
|
+
/**
|
|
38
|
+
* Get the native context for the current browser
|
|
39
|
+
*/
|
|
40
|
+
export declare function getNativeContext(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, currentBrowser: WebdriverIO.Browser, nativeContext: NativeContextType): boolean;
|
|
36
41
|
export {};
|
|
37
42
|
//# 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,EACR,OAAO,EACP,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EAC3B,MAAM,4BAA4B,CAAA;
|
|
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,EAC3B,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;AA4GD;;GAEG;AACH,wBAAsB,eAAe,CAAC,cAAc,EAAE,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA4DhG;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,CA0BnB;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"}
|
package/dist/utils.js
CHANGED
|
@@ -173,6 +173,17 @@ export function getBrowserObject(elem) {
|
|
|
173
173
|
* 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
|
|
174
174
|
*/
|
|
175
175
|
export function determineNativeContext(driver) {
|
|
176
|
+
// First check if it's multi remote
|
|
177
|
+
if (driver.isMultiremote) {
|
|
178
|
+
return Object.keys(driver).reduce((acc, instanceName) => {
|
|
179
|
+
const instance = driver[instanceName];
|
|
180
|
+
if (instance.sessionId) {
|
|
181
|
+
acc[instance.sessionId] = determineNativeContext(instance);
|
|
182
|
+
}
|
|
183
|
+
return acc;
|
|
184
|
+
}, {});
|
|
185
|
+
}
|
|
186
|
+
// If not check if it's a mobile
|
|
176
187
|
if (driver.isMobile) {
|
|
177
188
|
return !!driver.requestedCapabilities?.browserName === false
|
|
178
189
|
&& (driver.requestedCapabilities?.['appium:app'] !== undefined
|
|
@@ -180,5 +191,18 @@ export function determineNativeContext(driver) {
|
|
|
180
191
|
|| driver.requestedCapabilities?.['appium:appPackage'] !== undefined)
|
|
181
192
|
&& driver.requestedCapabilities?.['appium:autoWebview'] !== true;
|
|
182
193
|
}
|
|
194
|
+
// If not, it's webcontext
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get the native context for the current browser
|
|
199
|
+
*/
|
|
200
|
+
export function getNativeContext(browser, currentBrowser, nativeContext) {
|
|
201
|
+
if (browser.isMultiremote) {
|
|
202
|
+
return nativeContext[currentBrowser.sessionId];
|
|
203
|
+
}
|
|
204
|
+
else if (typeof nativeContext === 'boolean') {
|
|
205
|
+
return nativeContext;
|
|
206
|
+
}
|
|
183
207
|
return false;
|
|
184
208
|
}
|
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.
|
|
5
|
+
"version": "5.1.1",
|
|
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": "^8.
|
|
23
|
+
"@wdio/globals": "^8.39.1",
|
|
24
24
|
"@wdio/logger": "^8.38.0",
|
|
25
|
-
"@wdio/types": "^8.
|
|
25
|
+
"@wdio/types": "^8.39.0",
|
|
26
26
|
"node-fetch": "^3.3.2",
|
|
27
|
-
"webdriver-image-comparison": "^6.0.
|
|
27
|
+
"webdriver-image-comparison": "^6.0.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {},
|
|
30
30
|
"scripts": {
|