@wdio/visual-service 5.0.0 → 5.1.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 +165 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +37 -24
- 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 +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,170 @@
|
|
|
1
1
|
# @wdio/visual-service
|
|
2
2
|
|
|
3
|
+
## 5.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a0e29f2: Adding storybook interaction testing
|
|
8
|
+
|
|
9
|
+
### Storybook Interaction Testing
|
|
10
|
+
|
|
11
|
+
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:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { browser, expect } from "@wdio/globals";
|
|
15
|
+
|
|
16
|
+
describe("Storybook Interaction", () => {
|
|
17
|
+
it("should create screenshots for the logged in state when it logs out", async () => {
|
|
18
|
+
const componentId = "example-page--logged-in";
|
|
19
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
20
|
+
|
|
21
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
22
|
+
`${componentId}-logged-in-state`
|
|
23
|
+
);
|
|
24
|
+
await $("button=Log out").click();
|
|
25
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
26
|
+
`${componentId}-logged-out-state`
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should create screenshots for the logged out state when it logs in", async () => {
|
|
31
|
+
const componentId = "example-page--logged-out";
|
|
32
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
33
|
+
|
|
34
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
35
|
+
`${componentId}-logged-out-state`
|
|
36
|
+
);
|
|
37
|
+
await $("button=Log in").click();
|
|
38
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
39
|
+
`${componentId}-logged-in-state`
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
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).
|
|
46
|
+
|
|
47
|
+
The above spec file can be saved in a folder and added to the command line with the following command:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm run test.local.desktop.storybook.localhost -- --spec='tests/specs/storybook-interaction/*.ts'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
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:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npm run test.local.desktop.storybook.localhost -- --skipStories="/example-page.*/gm" --spec='tests/specs/storybook-interaction/*.ts'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### New Custom Command
|
|
60
|
+
|
|
61
|
+
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
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { browser, expect } from "@wdio/globals";
|
|
65
|
+
|
|
66
|
+
describe("Storybook Interaction", () => {
|
|
67
|
+
it("should create screenshots for the logged in state when it logs out", async () => {
|
|
68
|
+
const componentId = "example-page--logged-in";
|
|
69
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
70
|
+
|
|
71
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
72
|
+
`${componentId}-logged-in-state`
|
|
73
|
+
);
|
|
74
|
+
await $("button=Log out").click();
|
|
75
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
76
|
+
`${componentId}-logged-out-state`
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should create screenshots for the logged out state when it logs in", async () => {
|
|
81
|
+
const componentId = "example-page--logged-out";
|
|
82
|
+
await browser.waitForStorybookComponentToBeLoaded({ id: componentId });
|
|
83
|
+
|
|
84
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
85
|
+
`${componentId}-logged-out-state`
|
|
86
|
+
);
|
|
87
|
+
await $("button=Log in").click();
|
|
88
|
+
await expect($("header")).toMatchElementSnapshot(
|
|
89
|
+
`${componentId}-logged-in-state`
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The options are:
|
|
96
|
+
|
|
97
|
+
#### `clipSelector`
|
|
98
|
+
|
|
99
|
+
- **Type:** `string`
|
|
100
|
+
- **Mandatory:** No
|
|
101
|
+
- **Default:** `#storybook-root > :first-child` for Storybook V7 and `#root > :first-child:not(script):not(style)` for Storybook V6
|
|
102
|
+
- **Example:**
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
106
|
+
clipSelector: "#your-selector",
|
|
107
|
+
id: "componentId",
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This is the selector that will be used:
|
|
112
|
+
|
|
113
|
+
- to select the element to take the screenshot of
|
|
114
|
+
- for the element to wait to be visible before a screenshot is taken
|
|
115
|
+
|
|
116
|
+
#### `id`
|
|
117
|
+
|
|
118
|
+
- **Type:** `string`
|
|
119
|
+
- **Mandatory:** yes
|
|
120
|
+
- **Example:**
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
await browser.waitForStorybookComponentToBeLoaded({ '#your-selector', id: 'componentId' })
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
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`
|
|
127
|
+
|
|
128
|
+
#### `timeout`
|
|
129
|
+
|
|
130
|
+
- **Type:** `number`
|
|
131
|
+
- **Mandatory:** No
|
|
132
|
+
- **Default:** 1100 milliseconds
|
|
133
|
+
- **Example:**
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
137
|
+
id: "componentId",
|
|
138
|
+
timeout: 20000,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The max timeout we want to wait for a component to be visible after loading on the page
|
|
143
|
+
|
|
144
|
+
#### `url`
|
|
145
|
+
|
|
146
|
+
- **Type:** `string`
|
|
147
|
+
- **Mandatory:** No
|
|
148
|
+
- **Default:** `http://127.0.0.1:6006`
|
|
149
|
+
- **Example:**
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
await browser.waitForStorybookComponentToBeLoaded({
|
|
153
|
+
id: "componentId",
|
|
154
|
+
url: "https://your.url",
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The URL where your Storybook instance is hosted.
|
|
159
|
+
|
|
160
|
+
## 5.0.1
|
|
161
|
+
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- 169b7c5: fix(webdriver-image-comparison): export WicElement
|
|
165
|
+
- Updated dependencies [169b7c5]
|
|
166
|
+
- webdriver-image-comparison@6.0.1
|
|
167
|
+
|
|
3
168
|
## 5.0.0
|
|
4
169
|
|
|
5
170
|
### Major Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="./expect-webdriverio.js" />
|
|
2
|
-
import type { WicElement } from 'webdriver-image-comparison
|
|
2
|
+
import type { WicElement } from 'webdriver-image-comparison';
|
|
3
3
|
import WdioImageComparisonService from './service.js';
|
|
4
4
|
import VisualLauncher from './storybook/launcher.js';
|
|
5
5
|
import type { Output, Result, WdioCheckFullPageMethodOptions, WdioSaveFullPageMethodOptions, WdioSaveElementMethodOptions, WdioSaveScreenMethodOptions, WdioCheckElementMethodOptions, WdioCheckScreenMethodOptions } from './types.js';
|
|
6
|
+
import type { WaitForStorybookComponentToBeLoaded } from './storybook/Types.js';
|
|
6
7
|
declare global {
|
|
7
8
|
namespace WebdriverIO {
|
|
8
9
|
interface Browser {
|
|
@@ -38,6 +39,10 @@ declare global {
|
|
|
38
39
|
* Compares an image of the complete screen with the tabbable lines and dots
|
|
39
40
|
*/
|
|
40
41
|
checkTabbablePage(tag: string, checkTabbableOptions?: WdioCheckFullPageMethodOptions): Promise<Result>;
|
|
42
|
+
/**
|
|
43
|
+
* Waits for a component to be loaded
|
|
44
|
+
*/
|
|
45
|
+
waitForStorybookComponentToBeLoaded(options: WaitForStorybookComponentToBeLoaded): Promise<void>;
|
|
41
46
|
}
|
|
42
47
|
interface Element {
|
|
43
48
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
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,CAAqB;gBAEjC,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;IA4BjE,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI;IAMhC,YAAY,CAAE,WAAW,EAAC,MAAM,EAAE,KAAK,EAAC,MAAM,EAAE,EAAE,MAAM,EAAC,MAAM,GAAC,MAAM,EAAE,KAAK,EAAC,GAAG;CA4IpF"}
|
package/dist/service.js
CHANGED
|
@@ -4,6 +4,7 @@ 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
5
|
import { determineNativeContext, getFolders, getInstanceData } 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;
|
|
@@ -101,18 +103,23 @@ export default class WdioImageComparisonService extends BaseClass {
|
|
|
101
103
|
...Object.keys(elementCommands),
|
|
102
104
|
...Object.keys(pageCommands),
|
|
103
105
|
]) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
if (command === 'waitForStorybookComponentToBeLoaded') {
|
|
107
|
+
browser.addCommand(command, waitForStorybookComponentToBeLoaded);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
browser.addCommand(command, function (...args) {
|
|
111
|
+
const returnData = {};
|
|
112
|
+
for (const browserName of browserNames) {
|
|
113
|
+
const multiremoteBrowser = browser;
|
|
114
|
+
const browserInstance = multiremoteBrowser.getInstance(browserName);
|
|
115
|
+
/**
|
|
116
|
+
* casting command to `checkScreen` to simplify type handling here
|
|
117
|
+
*/
|
|
118
|
+
returnData[browserName] = browserInstance[command].call(browserInstance, ...args);
|
|
119
|
+
}
|
|
120
|
+
return returnData;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
async #addCommandsToBrowser(currentBrowser) {
|
|
@@ -136,18 +143,24 @@ export default class WdioImageComparisonService extends BaseClass {
|
|
|
136
143
|
}
|
|
137
144
|
for (const [commandName, command] of Object.entries(pageCommands)) {
|
|
138
145
|
log.info(`Adding element command "${commandName}" to browser object`);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
if (commandName === 'waitForStorybookComponentToBeLoaded') {
|
|
147
|
+
currentBrowser.addCommand(commandName, (options) => waitForStorybookComponentToBeLoaded(options));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
currentBrowser.addCommand(commandName, function (tag, pageOptions = {}) {
|
|
151
|
+
const options = {
|
|
152
|
+
executor: (script, ...varArgs) => {
|
|
153
|
+
return this.execute.bind(currentBrowser)(script, ...varArgs);
|
|
154
|
+
},
|
|
155
|
+
getElementRect: this.getElementRect.bind(currentBrowser),
|
|
156
|
+
screenShot: this.takeScreenshot.bind(currentBrowser),
|
|
157
|
+
};
|
|
158
|
+
return command(options, instanceData, getFolders(pageOptions, self.folders, self.#getBaselineFolder()), tag, {
|
|
159
|
+
wic: self.defaultOptions,
|
|
160
|
+
method: pageOptions,
|
|
161
|
+
}, self._isNativeContext);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
}
|
|
@@ -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":"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,EACb,mCAAmC,EACtC,MAAM,YAAY,CAAA;AAKnB;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,iBAUxD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY/C;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,CAMzE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsBlE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,GAAG,GAAG,CAcpF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,eAAe,UAqC/I;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QASvF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,iBAAiB,EAEjG,MAAM,oBAAa,GACpB,MAAM,CAIR;AAED;;GAEG;AACH,wBAAsB,mCAAmC,CACrD,OAAO,EAAE,mCAAmC,EAE5C,mBAAmB,yBAAkB,iBAyDxC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAMjF;AAWD;;GAEG;AACH,wBAAgB,eAAe,CAC3B,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,qBAAqB,EAEnI,cAAc,2BAAoB,EAClC,WAAW,wBAAiB,EAC5B,UAAU,uBAAgB,QAyB7B;AAED,wBAAgB,mCAAmC,CAC/C,EAAE,MAAM,EAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,kBAAkB,EACtE,UAAU,EAAE,OAAO,GACpB,WAAW,CAAC,YAAY,CAqB1B;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,MAAM,EAAE,EAClB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,MAAM,EAAE,EACjB,iBAAiB,EAAE,kBAAkB,EAAE,EACvC,iBAAiB,EAAE,OAAO,QAiB7B;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACvC,YAAY,EAAE,WAAW,CAAC,YAAY,EAAE,EAExC,uCAAuC,6CAAsC,EAC7E,4BAA4B,kCAA2B,QAsF1D;AAED;;GAEG;AACH,wBAAsB,aAAa,CAC/B,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,OAAO,EAAE,YAAY,EAErB,UAAU,sBAAe,EACzB,mBAAmB,iCAA0B,EAC7C,eAAe,qBAAc,EAC7B,kBAAkB,wBAAiB,GACpC,OAAO,CAAC,uBAAuB,CAAC,CA8BlC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,CAmBlF"}
|
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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ScreenshotOutput, ImageCompareResult, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, ClassOptions } from 'webdriver-image-comparison';
|
|
2
|
+
import type { RectanglesOutput } from 'webdriver-image-comparison/dist/methods/rectangles.interfaces.js';
|
|
3
|
+
import type { WaitForStorybookComponentToBeLoaded } from './storybook/Types.js';
|
|
2
4
|
type MultiOutput = {
|
|
3
5
|
[browserName: string]: ScreenshotOutput;
|
|
4
6
|
};
|
|
@@ -28,5 +30,14 @@ export interface WdioCheckScreenMethodOptions extends Omit<CheckScreenMethodOpti
|
|
|
28
30
|
}
|
|
29
31
|
export interface VisualServiceOptions extends ClassOptions {
|
|
30
32
|
}
|
|
33
|
+
export type PageCommandOptions = {
|
|
34
|
+
executor: <T>(script: string | ((...innerArgs: any[]) => unknown), ...varArgs: any[]) => Promise<T>;
|
|
35
|
+
getElementRect: (elementId: string) => Promise<RectanglesOutput>;
|
|
36
|
+
screenShot: () => Promise<string>;
|
|
37
|
+
};
|
|
38
|
+
export type PageCommand = (options: PageCommandOptions, ...args: any[]) => Promise<any>;
|
|
39
|
+
export interface PageCommands {
|
|
40
|
+
[commandName: string]: PageCommand | ((options: WaitForStorybookComponentToBeLoaded) => Promise<void>);
|
|
41
|
+
}
|
|
31
42
|
export {};
|
|
32
43
|
//# sourceMappingURL=types.d.ts.map
|
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;
|
|
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,gBAAgB,EAAE,MAAM,kEAAkE,CAAA;AACxG,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,sBAAsB,CAAA;AAE/E,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;AAEjE,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;CAAG;AAE7D,MAAM,MAAM,kBAAkB,GAAG;IAC7B,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACpG,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACjE,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AACxF,MAAM,WAAW,YAAY;IACzB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC,OAAO,EAAE,mCAAmC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CAC1G"}
|
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.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://webdriver.io/docs/visual-testing",
|
|
8
8
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@wdio/logger": "^8.38.0",
|
|
25
25
|
"@wdio/types": "^8.38.2",
|
|
26
26
|
"node-fetch": "^3.3.2",
|
|
27
|
-
"webdriver-image-comparison": "^6.0.
|
|
27
|
+
"webdriver-image-comparison": "^6.0.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {},
|
|
30
30
|
"scripts": {
|