@wdio/visual-service 6.3.3 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,8 +80,13 @@ export async function getStoriesJson(url) {
80
80
  ]);
81
81
  for (const response of [storiesRes, indexRes]) {
82
82
  if (response.ok) {
83
- const data = await response.json();
84
- return data.stories || data.entries;
83
+ try {
84
+ const data = await response.json();
85
+ return data.stories || data.entries;
86
+ }
87
+ catch (_ign) {
88
+ // Ignore the json parse error
89
+ }
85
90
  }
86
91
  }
87
92
  }
@@ -107,11 +112,15 @@ export function getArgvValue(argName, parseFunc) {
107
112
  }
108
113
  return undefined;
109
114
  }
115
+ /**
116
+ * Get the story baseline path for the given category and component
117
+ */
118
+ const getStoriesBaselinePathFn = ((category, component) => `./${category}/${component}/`);
110
119
  /**
111
120
  * Creates a it function for the test file
112
121
  * @TODO: improve this
113
122
  */
114
- export function itFunction({ clip, clipSelector, folders: { baselineFolder }, framework, skipStories, storyData, storybookUrl }) {
123
+ export function itFunction({ additionalSearchParams, clip, clipSelector, compareOptions, folders, framework, skipStories, storyData, storybookUrl, getStoriesBaselinePath = getStoriesBaselinePathFn }) {
115
124
  const { id } = storyData;
116
125
  const screenshotType = clip ? 'n element' : ' viewport';
117
126
  const DEFAULT_IT_TEXT = 'it';
@@ -130,8 +139,12 @@ export function itFunction({ clip, clipSelector, folders: { baselineFolder }, fr
130
139
  }
131
140
  // Setup the folder structure
132
141
  const { category, component } = extractCategoryAndComponent(id);
133
- const methodOptions = {
134
- baselineFolder: join(baselineFolder, `./${category}/${component}/`),
142
+ const storiesBaselinePath = getStoriesBaselinePath(category, component);
143
+ const checkMethodOptions = {
144
+ ...compareOptions,
145
+ actualFolder: join(folders.actualFolder, storiesBaselinePath),
146
+ baselineFolder: join(folders.baselineFolder, storiesBaselinePath),
147
+ diffFolder: join(folders.diffFolder, storiesBaselinePath),
135
148
  };
136
149
  const it = `
137
150
  ${itText}(\`should take a${screenshotType} screenshot of ${id}\`, async () => {
@@ -139,10 +152,11 @@ export function itFunction({ clip, clipSelector, folders: { baselineFolder }, fr
139
152
  clipSelector: '${clipSelector}',
140
153
  id: '${id}',
141
154
  storybookUrl: '${storybookUrl}',
155
+ additionalSearchParams: new URLSearchParams('${additionalSearchParams.toString()}'),
142
156
  });
143
157
  ${clip
144
- ? `await expect($('${clipSelector}')).toMatchElementSnapshot('${id}-element', ${JSON.stringify(methodOptions)})`
145
- : `await expect(browser).toMatchScreenSnapshot('${id}', ${JSON.stringify(methodOptions)})`}
158
+ ? `await expect($('${clipSelector}')).toMatchElementSnapshot('${id}-element', ${JSON.stringify(checkMethodOptions)})`
159
+ : `await expect(browser).toMatchScreenSnapshot('${id}', ${JSON.stringify(checkMethodOptions)})`}
146
160
  });
147
161
  `;
148
162
  return it;
@@ -164,10 +178,10 @@ export function writeTestFile(directoryPath, fileID, testContent) {
164
178
  /**
165
179
  * Create the test content
166
180
  */
167
- export function createTestContent({ clip, clipSelector, folders, framework, skipStories, stories, storybookUrl },
181
+ export function createTestContent({ additionalSearchParams, clip, clipSelector, compareOptions, folders, framework, getStoriesBaselinePath, skipStories, stories, storybookUrl },
168
182
  // For testing purposes only
169
183
  itFunc = itFunction) {
170
- const itFunctionOptions = { clip, clipSelector, folders, framework, skipStories, storybookUrl };
184
+ const itFunctionOptions = { additionalSearchParams, clip, clipSelector, compareOptions, folders, framework, getStoriesBaselinePath, skipStories, storybookUrl };
171
185
  return stories.reduce((acc, storyData) => acc + itFunc({ ...itFunctionOptions, storyData }), '');
172
186
  }
173
187
  /**
@@ -178,8 +192,16 @@ export async function waitForStorybookComponentToBeLoaded(options,
178
192
  isStorybookModeFunc = isStorybookMode) {
179
193
  const isStorybook = isStorybookModeFunc();
180
194
  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}`);
195
+ const { additionalSearchParams, clipSelector = process.env.VISUAL_STORYBOOK_CLIP_SELECTOR, id, url = process.env.VISUAL_STORYBOOK_URL, timeout = 11000, } = options;
196
+ const baseUrl = new URL('iframe.html', url);
197
+ const searchParams = new URLSearchParams({ id });
198
+ if (additionalSearchParams) {
199
+ for (const [key, value] of additionalSearchParams) {
200
+ searchParams.append(key, value);
201
+ }
202
+ }
203
+ baseUrl.search = searchParams.toString();
204
+ await browser.url(baseUrl.toString());
183
205
  await $(clipSelector).waitForDisplayed();
184
206
  await browser.executeAsync(async (timeout, done) => {
185
207
  let timedOut = false;
@@ -253,11 +275,11 @@ function filterStories(storiesJson) {
253
275
  /**
254
276
  * Create the test files
255
277
  */
256
- export function createTestFiles({ clip, clipSelector, directoryPath, folders, framework, numShards, skipStories, storiesJson, storybookUrl },
278
+ export function createTestFiles({ additionalSearchParams, clip, clipSelector, compareOptions, directoryPath, folders, framework, getStoriesBaselinePath, numShards, skipStories, storiesJson, storybookUrl },
257
279
  // For testing purposes only
258
280
  createTestCont = createTestContent, createFileD = createFileData, writeTestF = writeTestFile) {
259
281
  const fileNamePrefix = 'visual-storybook';
260
- const createTestContentData = { clip, clipSelector, folders, framework, skipStories, stories: storiesJson, storybookUrl };
282
+ const createTestContentData = { additionalSearchParams, clip, clipSelector, compareOptions, folders, framework, getStoriesBaselinePath, skipStories, stories: storiesJson, storybookUrl };
261
283
  if (numShards === 1) {
262
284
  const testContent = createTestCont(createTestContentData);
263
285
  const fileData = createFileD('All stories', testContent);
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { ScreenshotOutput, ImageCompareResult, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, ClassOptions } from 'webdriver-image-comparison';
1
+ import type { ScreenshotOutput, ImageCompareResult, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, ClassOptions, DeviceRectangles, TestContext, InstanceData } from 'webdriver-image-comparison';
2
2
  import type { ChainablePromiseElement } from 'webdriverio';
3
+ import type { ContextManager } from './contextManager.js';
3
4
  type MultiOutput = {
4
5
  [browserName: string]: ScreenshotOutput;
5
6
  };
@@ -8,19 +9,38 @@ type MultiResult = {
8
9
  [browserName: string]: ImageCompareResult | number;
9
10
  };
10
11
  export type Result = MultiResult | (ImageCompareResult | number);
11
- export type NativeContextType = boolean | Record<string, boolean>;
12
- export type MultiremoteCommandResult = {
13
- command: string;
14
- method: string;
15
- endpoint: string;
16
- body: Record<string, any>;
17
- result: {
18
- value: string;
19
- };
20
- sessionId: string | undefined;
21
- cid: string;
22
- type: string;
12
+ export type MobileInstanceData = {
13
+ devicePixelRatio: number;
14
+ deviceRectangles: DeviceRectangles;
23
15
  };
16
+ export type getFolderMethodOptions = CheckElementMethodOptions | CheckFullPageMethodOptions | CheckScreenMethodOptions | SaveElementMethodOptions | SaveFullPageMethodOptions | SaveScreenMethodOptions;
17
+ export type GetInstanceDataOptions = {
18
+ currentBrowser: WebdriverIO.Browser;
19
+ initialDeviceRectangles: DeviceRectangles;
20
+ isNativeContext: boolean;
21
+ };
22
+ export type EnrichTestContextOptions = {
23
+ commandName: string;
24
+ currentTestContext: TestContext;
25
+ instanceData: InstanceData;
26
+ tag: string;
27
+ };
28
+ export type GetMobileInstanceDataOptions = {
29
+ currentBrowser: WebdriverIO.Browser;
30
+ initialDeviceRectangles: DeviceRectangles;
31
+ isNativeContext: boolean;
32
+ nativeWebScreenshot: boolean;
33
+ };
34
+ export interface WrapWithContextOptions<T extends (...args: any[]) => any> {
35
+ browser: WebdriverIO.Browser;
36
+ command: T;
37
+ contextManager: ContextManager;
38
+ getArgs: () => Parameters<T>;
39
+ }
40
+ export interface WdioIcsOptions {
41
+ logName?: string;
42
+ name?: string;
43
+ }
24
44
  export interface WdioIcsCommonOptions {
25
45
  hideElements?: (WebdriverIO.Element | ChainablePromiseElement)[];
26
46
  removeElements?: (WebdriverIO.Element | ChainablePromiseElement)[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,YAAY,EACf,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAE1D,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,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;IACjE,cAAc,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;CACtE;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAC9D,oBAAoB,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC,EAAE,CAAC;CAC5E;AAED,MAAM,WAAW,8BACb,SAAQ,IAAI,CAAC,0BAA0B,EAAE,MAAM,oBAAoB,CAAC,EAChE,oBAAoB;CAAG;AAC/B,MAAM,WAAW,6BACb,SAAQ,IAAI,CAAC,yBAAyB,EAAE,MAAM,oBAAoB,CAAC,EAC/D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,4BACb,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,EAC9D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,2BACb,SAAQ,IAAI,CAAC,uBAAuB,EAAE,MAAM,oBAAoB,CAAC,EAC7D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,6BACb,SAAQ,IAAI,CAAC,yBAAyB,EAAE,MAAM,oBAAoB,CAAC,EAC/D,oBAAoB;CAAG;AAC/B,MAAM,WAAW,4BACb,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,EAC9D,oBAAoB;CAAG;AAE/B,MAAM,WAAW,oBAAqB,SAAQ,YAAY;CAAG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.ts CHANGED
@@ -1,17 +1,16 @@
1
- import type { Folders, InstanceData, CheckScreenMethodOptions, SaveScreenMethodOptions, CheckFullPageMethodOptions, SaveFullPageMethodOptions, CheckElementMethodOptions, SaveElementMethodOptions, TestContext } from 'webdriver-image-comparison';
2
- import type { NativeContextType } from './types.js';
1
+ import type { Folders, InstanceData, TestContext } from 'webdriver-image-comparison';
2
+ import type { EnrichTestContextOptions, getFolderMethodOptions, GetInstanceDataOptions } from './types.js';
3
3
  /**
4
4
  * Get the folders data
5
5
  *
6
6
  * If folder options are passed in use those values
7
7
  * Otherwise, use the values set during instantiation
8
8
  */
9
- type getFolderMethodOptions = CheckElementMethodOptions | CheckFullPageMethodOptions | CheckScreenMethodOptions | SaveElementMethodOptions | SaveFullPageMethodOptions | SaveScreenMethodOptions;
10
9
  export declare function getFolders(methodOptions: getFolderMethodOptions, folders: Folders, currentTestPath: string): Folders;
11
10
  /**
12
- * Get the size of a screenshot in pixels without the device pixel ratio
11
+ * Get the size of a base64 screenshot in pixels without the device pixel ratio
13
12
  */
14
- export declare function getScreenshotSize(screenshot: string, devicePixelRation?: number): {
13
+ export declare function getBase64ScreenshotSize(screenshot: string, devicePixelRation?: number): {
15
14
  height: number;
16
15
  width: number;
17
16
  };
@@ -29,27 +28,21 @@ export declare function getLtOptions(capabilities: WebdriverIO.Capabilities): an
29
28
  /**
30
29
  * Get the instance data
31
30
  */
32
- export declare function getInstanceData(currentBrowser: WebdriverIO.Browser): Promise<InstanceData>;
31
+ export declare function getInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext }: GetInstanceDataOptions): Promise<InstanceData>;
33
32
  /**
34
33
  * Traverse up the scope chain until browser element was reached
35
34
  */
36
35
  export declare function getBrowserObject(elem: WebdriverIO.Element | WebdriverIO.Browser): WebdriverIO.Browser;
36
+ export declare function getNativeContext({ capabilities, isMobile }: {
37
+ capabilities: WebdriverIO.Capabilities;
38
+ isMobile: boolean;
39
+ }): boolean;
37
40
  /**
38
- * 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
39
- */
40
- export declare function determineNativeContext(driver: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): NativeContextType;
41
- /**
42
- * Get the native context for the current browser
41
+ * Make sure we have all the data for the test context
43
42
  */
44
- export declare function getNativeContext(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, currentBrowser: WebdriverIO.Browser, nativeContext: NativeContextType): boolean;
43
+ export declare function enrichTestContext({ commandName, currentTestContext: { framework, parent, title, }, instanceData: { appName, browserName, browserVersion, deviceName, isAndroid, isIOS, isMobile, platformName, platformVersion, }, tag, }: EnrichTestContextOptions): TestContext;
45
44
  /**
46
- * Make sure we have all the data for the test context
45
+ * Check if the current browser supports isBidi screenshots
47
46
  */
48
- export declare function enrichTestContext({ commandName, currentTestContext: { framework, parent, title, }, instanceData: { appName, browserName, browserVersion, deviceName, isAndroid, isIOS, isMobile, platformName, platformVersion, }, tag, }: {
49
- commandName: string;
50
- currentTestContext: TestContext;
51
- instanceData: InstanceData;
52
- tag: string;
53
- }): TestContext;
54
- export {};
47
+ export declare function isBiDiScreenshotSupported(driver: WebdriverIO.Browser): boolean;
55
48
  //# sourceMappingURL=utils.d.ts.map
@@ -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,EACxB,WAAW,EACd,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;AAkFD;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,YAAY,GAAG,GAAG,GAAG,SAAS,CAMpF;AAmCD;;GAEG;AACH,wBAAsB,eAAe,CAAC,cAAc,EAAE,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA+DhG;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,CAmDnB;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;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,EACI,WAAW,EACX,kBAAkB,EAAE,EAChB,SAAS,EACT,MAAM,EACN,KAAK,GACR,EACD,YAAY,EAAE,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,eAAe,GAClB,EACD,GAAG,GACN,EACD;IACI,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,WAAW,CAAC;IAChC,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;CACf,GACF,WAAW,CAuBb"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACpF,OAAO,KAAK,EACR,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EAIzB,MAAM,YAAY,CAAA;AAEnB;;;;;GAKG;AAEH,wBAAgB,UAAU,CACtB,aAAa,EAAE,sBAAsB,EACrC,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,MAAM,GACxB,OAAO,CAMT;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,iBAAiB,SAAI,GAAG;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB,CAKA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE;IAAC,MAAM,EAAC,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAAG,MAAM,CAOhH;AAiGD;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,YAAY,GAAG,GAAG,GAAG,SAAS,CAMpF;AAmCD;;GAEG;AACH,wBAAsB,eAAe,CAAC,EAClC,cAAc,EACd,uBAAuB,EACvB,eAAe,EAClB,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAgEhD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAE,IAAI,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,CAGtG;AAOD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,EACvD;IAAE,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAC9D,OAAO,CAuBT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,EACI,WAAW,EACX,kBAAkB,EAAE,EAChB,SAAS,EACT,MAAM,EACN,KAAK,GACR,EACD,YAAY,EAAE,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,eAAe,GAClB,EACD,GAAG,GACN,EAAE,wBAAwB,GAAG,WAAW,CAuB5C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,GAAG,OAAO,CAK9E"}
package/dist/utils.js CHANGED
@@ -1,5 +1,10 @@
1
- import { IOS_OFFSETS } from 'webdriver-image-comparison';
2
- import { NOT_KNOWN } from 'webdriver-image-comparison/dist/helpers/constants.js';
1
+ import { getMobileScreenSize, getMobileViewPortPosition, IOS_OFFSETS, NOT_KNOWN } from 'webdriver-image-comparison';
2
+ /**
3
+ * Get the folders data
4
+ *
5
+ * If folder options are passed in use those values
6
+ * Otherwise, use the values set during instantiation
7
+ */
3
8
  export function getFolders(methodOptions, folders, currentTestPath) {
4
9
  return {
5
10
  actualFolder: methodOptions.actualFolder ?? folders.actualFolder,
@@ -8,9 +13,9 @@ export function getFolders(methodOptions, folders, currentTestPath) {
8
13
  };
9
14
  }
10
15
  /**
11
- * Get the size of a screenshot in pixels without the device pixel ratio
16
+ * Get the size of a base64 screenshot in pixels without the device pixel ratio
12
17
  */
13
- export function getScreenshotSize(screenshot, devicePixelRation = 1) {
18
+ export function getBase64ScreenshotSize(screenshot, devicePixelRation = 1) {
14
19
  return {
15
20
  height: Buffer.from(screenshot, 'base64').readUInt32BE(20) / devicePixelRation,
16
21
  width: Buffer.from(screenshot, 'base64').readUInt32BE(16) / devicePixelRation,
@@ -20,7 +25,7 @@ export function getScreenshotSize(screenshot, devicePixelRation = 1) {
20
25
  * Get the device pixel ratio
21
26
  */
22
27
  export function getDevicePixelRatio(screenshot, deviceScreenSize) {
23
- const screenshotSize = getScreenshotSize(screenshot);
28
+ const screenshotSize = getBase64ScreenshotSize(screenshot);
24
29
  const devicePixelRatio = Math.round(screenshotSize.width / deviceScreenSize.width) === Math.round(screenshotSize.height / deviceScreenSize.height)
25
30
  ? Math.round(screenshotSize.width / deviceScreenSize.width)
26
31
  : Math.round(screenshotSize.height / deviceScreenSize.width);
@@ -29,21 +34,42 @@ export function getDevicePixelRatio(screenshot, deviceScreenSize) {
29
34
  /**
30
35
  * Get the mobile instance data
31
36
  */
32
- async function getMobileInstanceData({ currentBrowser, isAndroid, isMobile }) {
33
- const deviceScreenSize = {
34
- height: 0,
35
- width: 0,
36
- };
37
- const devicePlatformRect = {
38
- statusBar: { height: 0, x: 0, width: 0, y: 0 },
39
- homeBar: { height: 0, x: 0, width: 0, y: 0 },
40
- };
37
+ async function getMobileInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext, nativeWebScreenshot, }) {
38
+ const { isAndroid, isIOS, isMobile } = currentBrowser;
41
39
  let devicePixelRatio = 1;
40
+ let deviceRectangles = initialDeviceRectangles;
42
41
  if (isMobile) {
42
+ const executor = (fn, ...args) => currentBrowser.execute(fn, ...args);
43
+ const getUrl = () => currentBrowser.getUrl();
44
+ const url = (arg) => currentBrowser.url(arg);
43
45
  const currentDriverCapabilities = currentBrowser.capabilities;
44
- const { height, width } = await currentBrowser.getWindowSize();
45
- deviceScreenSize.height = height;
46
- deviceScreenSize.width = width;
46
+ const { height: screenHeight, width: screenWidth } = await getMobileScreenSize({
47
+ currentBrowser,
48
+ executor,
49
+ isIOS,
50
+ isNativeContext,
51
+ });
52
+ // Update the width for the device rectangles for bottomBar, screenSize, statusBar, statusBarAndAddressBar
53
+ deviceRectangles.screenSize.height = screenHeight;
54
+ deviceRectangles.screenSize.width = screenWidth;
55
+ deviceRectangles.bottomBar.width = screenWidth;
56
+ deviceRectangles.statusBarAndAddressBar.width = screenWidth;
57
+ deviceRectangles.statusBar.width = screenWidth;
58
+ deviceRectangles = await getMobileViewPortPosition({
59
+ initialDeviceRectangles,
60
+ isAndroid,
61
+ isIOS,
62
+ isNativeContext,
63
+ methods: {
64
+ executor,
65
+ getUrl,
66
+ url,
67
+ },
68
+ nativeWebScreenshot,
69
+ screenHeight,
70
+ screenWidth,
71
+ });
72
+ // @TODO: 20250317: When we have all things tested with the above, we can simplify the below part to only use the iOS part
47
73
  // @TODO: This is al based on PORTRAIT mode
48
74
  if (isAndroid && currentDriverCapabilities) {
49
75
  // We use a few `@ts-ignore` here because `pixelRatio` and `statBarHeight`
@@ -56,35 +82,38 @@ async function getMobileInstanceData({ currentBrowser, isAndroid, isMobile }) {
56
82
  // @ts-ignore
57
83
  if (currentDriverCapabilities?.statBarHeight !== undefined) {
58
84
  // @ts-ignore
59
- devicePlatformRect.statusBar.height = currentDriverCapabilities?.statBarHeight;
60
- devicePlatformRect.statusBar.width = width;
85
+ deviceRectangles.statusBar.height = currentDriverCapabilities?.statBarHeight;
86
+ deviceRectangles.statusBar.width = deviceRectangles.screenSize.width;
61
87
  }
62
88
  }
63
89
  else {
64
90
  // This is to already determine the device pixel ratio if it's not set in the capabilities
65
91
  const base64Image = await currentBrowser.takeScreenshot();
66
- devicePixelRatio = getDevicePixelRatio(base64Image, deviceScreenSize);
67
- const isIphone = width < 1024 && height < 1024;
92
+ devicePixelRatio = getDevicePixelRatio(base64Image, deviceRectangles.screenSize);
93
+ const isIphone = deviceRectangles.screenSize.width < 1024 && deviceRectangles.screenSize.height < 1024;
68
94
  const deviceType = isIphone ? 'IPHONE' : 'IPAD';
69
95
  const defaultPortraitHeight = isIphone ? 667 : 1024;
70
- const portraitHeight = width > height ? width : height;
71
- const offsetPortraitHeight = Object.keys(IOS_OFFSETS[deviceType]).indexOf(portraitHeight.toString()) > -1 ? portraitHeight : defaultPortraitHeight;
96
+ const portraitHeight = deviceRectangles.screenSize.width > deviceRectangles.screenSize.height ?
97
+ deviceRectangles.screenSize.width :
98
+ deviceRectangles.screenSize.height;
99
+ const offsetPortraitHeight = Object.keys(IOS_OFFSETS[deviceType]).indexOf(portraitHeight.toString()) > -1 ?
100
+ portraitHeight :
101
+ defaultPortraitHeight;
72
102
  const currentOffsets = IOS_OFFSETS[deviceType][offsetPortraitHeight].PORTRAIT;
73
103
  // NOTE: The values for iOS are based on CSS pixels, so we need to multiply them with the devicePixelRatio,
74
104
  // This will NOT be done here but in a central place
75
- devicePlatformRect.statusBar = {
76
- y: 0,
105
+ deviceRectangles.statusBar = {
77
106
  x: 0,
78
- width,
107
+ y: 0,
108
+ width: deviceRectangles.screenSize.width,
79
109
  height: currentOffsets.STATUS_BAR,
80
110
  };
81
- devicePlatformRect.homeBar = currentOffsets.HOME_BAR;
111
+ deviceRectangles.homeBar = currentOffsets.HOME_BAR;
82
112
  }
83
113
  }
84
114
  return {
85
115
  devicePixelRatio,
86
- devicePlatformRect,
87
- deviceScreenSize,
116
+ deviceRectangles,
88
117
  };
89
118
  }
90
119
  /**
@@ -125,8 +154,7 @@ function getDeviceName(currentBrowser) {
125
154
  /**
126
155
  * Get the instance data
127
156
  */
128
- export async function getInstanceData(currentBrowser) {
129
- const NOT_KNOWN = 'not-known';
157
+ export async function getInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext }) {
130
158
  const { capabilities: currentCapabilities, requestedCapabilities } = currentBrowser;
131
159
  const { browserName: rawBrowserName = NOT_KNOWN, browserVersion: rawBrowserVersion = NOT_KNOWN, platformName: rawPlatformName = NOT_KNOWN, } = currentCapabilities;
132
160
  // Generic data
@@ -158,7 +186,7 @@ export async function getInstanceData(currentBrowser) {
158
186
  // 20241216: LT doesn't have the option to take a ChromeDriver screenshot, so if it's Android it's always native
159
187
  const nativeWebScreenshot = isAndroid && ltOptions || !!(requestedCapabilities['appium:nativeWebScreenshot']);
160
188
  const platformVersion = (rawPlatformVersion === undefined || rawPlatformVersion === '') ? NOT_KNOWN : rawPlatformVersion.toLowerCase();
161
- const { devicePixelRatio: mobileDevicePixelRatio, devicePlatformRect, deviceScreenSize, } = await getMobileInstanceData({ currentBrowser, isAndroid, isMobile });
189
+ const { devicePixelRatio: mobileDevicePixelRatio, deviceRectangles, } = await getMobileInstanceData({ currentBrowser, initialDeviceRectangles, isNativeContext, nativeWebScreenshot });
162
190
  devicePixelRatio = isMobile ? mobileDevicePixelRatio : devicePixelRatio;
163
191
  return {
164
192
  appName,
@@ -166,8 +194,7 @@ export async function getInstanceData(currentBrowser) {
166
194
  browserVersion,
167
195
  deviceName,
168
196
  devicePixelRatio,
169
- devicePlatformRect,
170
- deviceScreenSize,
197
+ deviceRectangles,
171
198
  isAndroid,
172
199
  isIOS,
173
200
  isMobile,
@@ -185,65 +212,28 @@ export function getBrowserObject(elem) {
185
212
  const elemObject = elem;
186
213
  return elemObject.parent ? getBrowserObject(elemObject.parent) : elem;
187
214
  }
188
- /**
189
- * We can't say it's native context if the autoWebview is provided and set to true, for all other cases we can say it's native
190
- */
191
- export function determineNativeContext(driver) {
192
- // First check if it's multi remote
193
- if (driver.isMultiremote) {
194
- return Object.keys(driver).reduce((acc, instanceName) => {
195
- const instance = driver[instanceName];
196
- if (instance.sessionId) {
197
- acc[instance.sessionId] = determineNativeContext(instance);
198
- }
199
- return acc;
200
- }, {});
201
- }
202
- // If not check if it's a mobile
203
- if (driver.isMobile) {
204
- const isAppiumAppCapPresent = (capabilities) => {
205
- const appiumKeys = [
206
- 'appium:app',
207
- 'appium:bundleId',
208
- 'appium:appPackage',
209
- 'appium:appActivity',
210
- 'appium:appWaitActivity',
211
- 'appium:appWaitPackage',
212
- 'appium:autoWebview',
213
- ];
214
- const optionsKeys = appiumKeys.map(key => key.replace('appium:', ''));
215
- const isInRoot = appiumKeys.some(key => capabilities[key] !== undefined);
216
- // @ts-expect-error
217
- const isInAppiumOptions = capabilities['appium:options'] &&
218
- // @ts-expect-error
219
- optionsKeys.some(key => capabilities['appium:options']?.[key] !== undefined);
220
- // @ts-expect-error
221
- const isInLtOptions = capabilities['lt:options'] &&
222
- // @ts-expect-error
223
- optionsKeys.some(key => capabilities['lt:options']?.[key] !== undefined);
224
- return !!(isInRoot || isInAppiumOptions || isInLtOptions);
225
- };
226
- const capabilities = driver.requestedCapabilities;
227
- const isBrowserNameFalse = !!capabilities.browserName === false;
228
- const isAutoWebviewFalse = !(capabilities['appium:autoWebview'] === true ||
229
- capabilities['appium:options']?.autoWebview === true ||
230
- capabilities['lt:options']?.autoWebview === true);
231
- return isBrowserNameFalse && isAppiumAppCapPresent(capabilities) && isAutoWebviewFalse;
232
- }
233
- // If not, it's webcontext
234
- return false;
235
- }
236
215
  /**
237
216
  * Get the native context for the current browser
238
217
  */
239
- export function getNativeContext(browser, currentBrowser, nativeContext) {
240
- if (browser.isMultiremote) {
241
- return nativeContext[currentBrowser.sessionId];
218
+ const appiumKeys = ['app', 'bundleId', 'appPackage', 'appActivity', 'appWaitActivity', 'appWaitPackage'];
219
+ export function getNativeContext({ capabilities, isMobile }) {
220
+ if (!capabilities || typeof capabilities !== 'object' || !isMobile) {
221
+ return false;
242
222
  }
243
- else if (typeof nativeContext === 'boolean') {
244
- return nativeContext;
245
- }
246
- return false;
223
+ const isAppiumAppCapPresent = (capabilities) => {
224
+ return appiumKeys.some((key) => (capabilities[key] !== undefined ||
225
+ capabilities[`appium:${key}`] !== undefined ||
226
+ capabilities['appium:options']?.[key] !== undefined ||
227
+ capabilities['lt:options']?.[key] !== undefined));
228
+ };
229
+ const isBrowserNameFalse = !!capabilities?.browserName === false;
230
+ const isAutoWebviewFalse = !(
231
+ // @ts-expect-error
232
+ capabilities?.autoWebview === true ||
233
+ capabilities['appium:autoWebview'] === true ||
234
+ capabilities['appium:options']?.autoWebview === true ||
235
+ capabilities['lt:options']?.autoWebview === true);
236
+ return isBrowserNameFalse && isAppiumAppCapPresent(capabilities) && isAutoWebviewFalse;
247
237
  }
248
238
  /**
249
239
  * Make sure we have all the data for the test context
@@ -272,3 +262,11 @@ export function enrichTestContext({ commandName, currentTestContext: { framework
272
262
  title,
273
263
  };
274
264
  }
265
+ /**
266
+ * Check if the current browser supports isBidi screenshots
267
+ */
268
+ export function isBiDiScreenshotSupported(driver) {
269
+ const { isBidi } = driver;
270
+ const isBiDiSupported = typeof driver.browsingContextCaptureScreenshot === 'function';
271
+ return isBidi && isBiDiSupported;
272
+ }
@@ -0,0 +1,8 @@
1
+ import type { WrapWithContextOptions } from './types.js';
2
+ /**
3
+ * Wrap the command with the context manager
4
+ * This will make sure that the context manager is updated when needed
5
+ * and that the command is executed in the correct context
6
+ */
7
+ export declare function wrapWithContext<T extends (...args: any[]) => any>(opts: WrapWithContextOptions<T>): () => Promise<ReturnType<T>>;
8
+ //# sourceMappingURL=wrapWithContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapWithContext.d.ts","sourceRoot":"","sources":["../src/wrapWithContext.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAGxD;;;;GAIG;AAEH,wBAAgB,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAkBhI"}
@@ -0,0 +1,21 @@
1
+ import { getInstanceData } from './utils.js';
2
+ /**
3
+ * Wrap the command with the context manager
4
+ * This will make sure that the context manager is updated when needed
5
+ * and that the command is executed in the correct context
6
+ */
7
+ export function wrapWithContext(opts) {
8
+ const { browser, command, contextManager, getArgs } = opts;
9
+ return async function () {
10
+ if (contextManager.needsUpdate) {
11
+ const instanceData = await getInstanceData({
12
+ currentBrowser: browser,
13
+ initialDeviceRectangles: contextManager.getViewportContext(),
14
+ isNativeContext: contextManager.isNativeContext,
15
+ });
16
+ contextManager.setViewPortContext(instanceData.deviceRectangles);
17
+ }
18
+ const finalArgs = getArgs();
19
+ return command.apply(this, finalArgs);
20
+ };
21
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@wdio/visual-service",
3
3
  "author": "Wim Selles - wswebcreation",
4
4
  "description": "Image comparison / visual regression testing for WebdriverIO",
5
- "version": "6.3.3",
5
+ "version": "7.0.0",
6
6
  "license": "MIT",
7
7
  "homepage": "https://webdriver.io/docs/visual-testing",
8
8
  "repository": {
@@ -20,12 +20,12 @@
20
20
  "type": "module",
21
21
  "types": "./dist/index.d.ts",
22
22
  "dependencies": {
23
- "@wdio/globals": "^9.9.1",
23
+ "@wdio/globals": "^9.12.6",
24
24
  "@wdio/logger": "^9.4.4",
25
- "@wdio/types": "^9.9.0",
26
- "webdriver-image-comparison": "^7.3.2"
25
+ "@wdio/types": "^9.12.6",
26
+ "expect-webdriverio": "^5.1.0",
27
+ "webdriver-image-comparison": "8.0.0"
27
28
  },
28
- "devDependencies": {},
29
29
  "scripts": {
30
30
  "build": "run-s clean build:*",
31
31
  "build:tsc": "tsc --project ./tsconfig.json",