@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 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
  }
@@ -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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"}
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;
@@ -1 +1 @@
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"}
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"}
@@ -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,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
- this._isNativeContext = result.includes('NATIVE');
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
- log.info(`Adding commands to Multi Browser: ${browserNames.join(', ')}`);
99
+ /**
100
+ * Add all the commands to each browser in the Multi Remote
101
+ */
91
102
  for (const browserName of browserNames) {
92
- const multiremoteBrowser = browser;
93
- const browserInstance = multiremoteBrowser.getInstance(browserName);
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
- ...Object.keys(elementCommands),
102
- ...Object.keys(pageCommands),
103
- ]) {
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
- });
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 self = this;
128
+ const isNativeContext = getNativeContext(this._browser, currentBrowser, this._isNativeContext);
121
129
  for (const [commandName, command] of Object.entries(elementCommands)) {
122
- log.info(`Adding element command "${commandName}" to browser object`);
123
- currentBrowser.addCommand(commandName, function (element, tag, elementOptions = {}) {
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(currentBrowser)(script, ...varArgs);
169
+ return this.execute.bind(browser)(script, ...varArgs);
127
170
  },
128
- getElementRect: this.getElementRect.bind(currentBrowser),
129
- screenShot: this.takeScreenshot.bind(currentBrowser),
130
- takeElementScreenshot: this.takeElementScreenshot.bind(currentBrowser),
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: elementOptions,
134
- }, self._isNativeContext);
175
+ method: pageOptions,
176
+ }, isNativeContext);
135
177
  });
136
178
  }
137
- for (const [commandName, command] of Object.entries(pageCommands)) {
138
- log.info(`Adding element command "${commandName}" to browser object`);
139
- currentBrowser.addCommand(commandName, function (tag, pageOptions = {}) {
140
- return command({
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 this.execute.bind(currentBrowser)(script, ...varArgs);
219
+ return browserInstance.execute.bind(browserInstance)(script, ...varArgs);
143
220
  },
144
- getElementRect: this.getElementRect.bind(currentBrowser),
145
- screenShot: this.takeScreenshot.bind(currentBrowser),
146
- }, instanceData, getFolders(pageOptions, self.folders, self.#getBaselineFolder()), tag, {
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
- }, self._isNativeContext);
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: 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
@@ -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[];
@@ -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;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): boolean;
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
@@ -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;AAQnC;;;;;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,OAAO,CAaT"}
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.0.1",
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.38.2",
23
+ "@wdio/globals": "^8.39.1",
24
24
  "@wdio/logger": "^8.38.0",
25
- "@wdio/types": "^8.38.2",
25
+ "@wdio/types": "^8.39.0",
26
26
  "node-fetch": "^3.3.2",
27
- "webdriver-image-comparison": "^6.0.1"
27
+ "webdriver-image-comparison": "^6.0.2"
28
28
  },
29
29
  "devDependencies": {},
30
30
  "scripts": {