@wdio/visual-service 5.0.1 → 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 CHANGED
@@ -1,5 +1,162 @@
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
+
3
160
  ## 5.0.1
4
161
 
5
162
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ 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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -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;AAoBnC,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;CAgIpF"}
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
- browser.addCommand(command, function (...args) {
105
- const returnData = {};
106
- for (const browserName of browserNames) {
107
- const multiremoteBrowser = browser;
108
- const browserInstance = multiremoteBrowser.getInstance(browserName);
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
- });
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
- currentBrowser.addCommand(commandName, function (tag, pageOptions = {}) {
140
- return command({
141
- executor: (script, ...varArgs) => {
142
- return this.execute.bind(currentBrowser)(script, ...varArgs);
143
- },
144
- getElementRect: this.getElementRect.bind(currentBrowser),
145
- screenShot: this.takeScreenshot.bind(currentBrowser),
146
- }, instanceData, getFolders(pageOptions, self.folders, self.#getBaselineFolder()), tag, {
147
- wic: self.defaultOptions,
148
- method: pageOptions,
149
- }, self._isNativeContext);
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: Stories;
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: Stories;
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":"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"}
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;IAmEpF,UAAU;CAenB"}
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":"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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/storybook/utils.ts"],"names":[],"mappings":"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"}
@@ -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.url(\`${storybookUrl}iframe.html?id=${id}\`);
139
- await $('${clipSelector}').waitForDisplayed();
140
- await waitForAllImagesLoaded();
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
- const waitForAllImagesLoaded = `
172
- async function waitForAllImagesLoaded() {
173
- await browser.executeAsync(async (done) => {
174
- const timeout = 11000; // 11 seconds
175
- let timedOut = false;
176
-
177
- const timeoutPromise = new Promise((resolve, reject) => {
178
- setTimeout(() => {
179
- timedOut = true;
180
- reject('Timeout: Not all images loaded within 11 seconds');
181
- }, timeout);
182
- });
183
-
184
- const isImageLoaded = (img) => img.complete && img.naturalWidth > 0;
185
-
186
- // Check for <img> elements
187
- const imgElements = Array.from(document.querySelectorAll('img'));
188
- const imgPromises = imgElements.map(img => isImageLoaded(img) ? Promise.resolve() : new Promise(resolve => {
189
- img.onload = () => !timedOut && resolve();
190
- img.onerror = () => !timedOut && resolve();
191
- }));
192
-
193
- // Check for CSS background images
194
- const allElements = Array.from(document.querySelectorAll('*'));
195
- const bgImagePromises = allElements.map(el => {
196
- const bgImage = window.getComputedStyle(el).backgroundImage;
197
- if (bgImage && bgImage !== 'none' && bgImage.startsWith('url')) {
198
- const imageUrl = bgImage.slice(5, -2); // Extract URL from the 'url("")'
199
- const image = new Image();
200
- image.src = imageUrl;
201
- return isImageLoaded(image) ? Promise.resolve() : new Promise(resolve => {
202
- image.onload = () => !timedOut && resolve();
203
- image.onerror = () => !timedOut && resolve();
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
- return Promise.resolve();
207
- });
208
-
209
- try {
210
- await Promise.race([Promise.all([...imgPromises, ...bgImagePromises]), timeoutPromise]);
211
- done();
212
- } catch (error) {
213
- done(error);
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: storiesArray, storybookUrl };
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 = storiesArray.length;
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 = storiesArray.slice(startIndex, endIndex);
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
@@ -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;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"}
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.0.1",
5
+ "version": "5.1.0",
6
6
  "license": "MIT",
7
7
  "homepage": "https://webdriver.io/docs/visual-testing",
8
8
  "repository": {