@wdio/visual-service 1.0.0 โ†’ 2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,84 @@
1
1
  # @wdio/visual-service
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - ef386b6: # ๐Ÿ’ฅ Breaking changes:
8
+
9
+ - `resizeDimensions` on the element can now only be an object, it has been deprecated for a while
10
+
11
+ # ๐Ÿ’… New Features
12
+
13
+ - Next to supporting Web snapshot testing this module now also supports ๐Ÿ’ฅ **Native App** ๐Ÿ’ฅ snapshot testing. The methods `saveElement|checkElement | saveScreen | checkScreen` and the matchers `toMatchElementSnapshot | toMatchScreenSnapshot` are available for **Native Apps**
14
+
15
+ > [!NOTE]
16
+ > This module will automatically detect the context (web | webview | native_app) and will handle all complex logic for you
17
+
18
+ The methods `saveFullPageScreen | checkFullPageScreen | saveTabbablePage|checkTabbablePage` will throw an error when they are used in the native context for native mobile apps and will look like this
19
+
20
+ ```logs
21
+ $ wdio tests/configs/wdio.local.android.emus.app.conf.ts
22
+
23
+ Execution of 1 workers started at 2024-01-30T06:18:24.865Z
24
+
25
+ [0-0] RUNNING in Android - file:///tests/specs/mobile.app.spec.ts
26
+ [0-0] Error in "@wdio/visual-service mobile app.should compare a screen successful for 'Pixel_7_Pro_Android_14_API_34' in PORTRAIT-mode"
27
+ Error: The method saveFullPageScreen is not supported in native context for native mobile apps!
28
+ at /wdio/visual-testing/packages/webdriver-image-comparison/src/commands/saveFullPageScreen.ts:26:15
29
+ at step (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:33:23)
30
+ at Object.next (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:14:53)
31
+ at /wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:8:71
32
+ at new Promise (<anonymous>)
33
+ at __awaiter (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:4:12)
34
+ at saveFullPageScreen (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:47:12)
35
+ at Browser.<anonymous> (file:///wdio/visual-testing/packages/service/dist/service.js:101:24)
36
+ [0-0] FAILED in Android - file:///tests/specs/mobile.app.spec.ts
37
+
38
+ "spec" Reporter:
39
+ ------------------------------------------------------------------
40
+ [/wdio/visual-testing/apps/app.apk Android #0-0] Running: /wdio/visual-testing/apps/app.apk on Android
41
+ [/wdio/visual-testing/apps/app.apk Android #0-0] Session ID: c1101184-e3d5-42b5-a31f-8ebaa211f1a1
42
+ [/wdio/visual-testing/apps/app.apk Android #0-0]
43
+ [/wdio/visual-testing/apps/app.apk Android #0-0] ยป /tests/specs/mobile.app.spec.ts
44
+ [/wdio/visual-testing/apps/app.apk Android #0-0] @wdio/visual-service mobile app
45
+ [/wdio/visual-testing/apps/app.apk Android #0-0] โœ– should compare a screen successful for 'Pixel_7_Pro_Android_14_API_34' in PORTRAIT-mode
46
+ [/wdio/visual-testing/apps/app.apk Android #0-0]
47
+ [/wdio/visual-testing/apps/app.apk Android #0-0] 1 failing (1.5s)
48
+ [/wdio/visual-testing/apps/app.apk Android #0-0]
49
+ [/wdio/visual-testing/apps/app.apk Android #0-0] 1) @wdio/visual-service mobile app should compare a screen successful for 'Pixel_7_Pro_Android_14_API_34' in PORTRAIT-mode
50
+ [/wdio/visual-testing/apps/app.apk Android #0-0] The method saveFullPageScreen is not supported in native context for native mobile apps!
51
+ [/wdio/visual-testing/apps/app.apk Android #0-0] Error: The method saveFullPageScreen is not supported in native context for native mobile apps!
52
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at /wdio/visual-testing/packages/webdriver-image-comparison/src/commands/saveFullPageScreen.ts:26:15
53
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at step (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:33:23)
54
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at Object.next (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:14:53)
55
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at /wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:8:71
56
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at new Promise (<anonymous>)
57
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at __awaiter (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:4:12)
58
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at saveFullPageScreen (/wdio/visual-testing/packages/webdriver-image-comparison/dist/commands/saveFullPageScreen.js:47:12)
59
+ [/wdio/visual-testing/apps/app.apk Android #0-0] at Browser.<anonymous> (file:///wdio/visual-testing/packages/service/dist/service.js:101:24)
60
+
61
+
62
+ Spec Files: 0 passed, 1 failed, 1 total (100% completed) in 00:00:11
63
+
64
+ error Command failed with exit code 1.
65
+ ```
66
+
67
+ - `autoSaveBaseline` is true by default, so if no baseline images are present it will automatically create a new baseline
68
+ - Mobile screenshots of the complete screen now automatically exclude all native OS elements like the notification bar, home bar, address bar, and so on, the settings `blockOutSideBar | blockOutStatusBar |blockOutToolBar` are now all defaulted to `true`
69
+ -
70
+
71
+ # ๐Ÿ› Fixed bugs:
72
+
73
+ - element screenshots could also get resized dimensions, which would cut out a bigger portion around the element. This was failing when the dimensions got out of the boundaries of the official screenshot. This has now been fixed with:
74
+ - not going outside of the screenshot
75
+ - log extra warnings
76
+
77
+ ### Patch Changes
78
+
79
+ - Updated dependencies [ef386b6]
80
+ - webdriver-image-comparison@4.0.0
81
+
3
82
  ## 1.0.0
4
83
 
5
84
  ### Major Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  /// <reference types="./expect-webdriverio.js" />
2
+ import type { WicElement } from 'webdriver-image-comparison/dist/commands/element.interfaces.js';
2
3
  import WdioImageComparisonService from './service.js';
3
4
  import type { Output, Result, WdioCheckFullPageMethodOptions, WdioSaveFullPageMethodOptions, WdioSaveElementMethodOptions, WdioSaveScreenMethodOptions, WdioCheckElementMethodOptions, WdioCheckScreenMethodOptions } from './types.js';
4
5
  declare global {
@@ -7,7 +8,7 @@ declare global {
7
8
  /**
8
9
  * Saves an image of an element
9
10
  */
10
- saveElement(element: Element, tag: string, saveElementOptions?: WdioSaveElementMethodOptions): Promise<Output>;
11
+ saveElement(element: WicElement, tag: string, saveElementOptions?: WdioSaveElementMethodOptions): Promise<Output>;
11
12
  /**
12
13
  * Saves an image of a viewport
13
14
  */
@@ -23,7 +24,7 @@ declare global {
23
24
  /**
24
25
  * Compare an image of an element
25
26
  */
26
- checkElement(element: Element, tag: string, checkElementOptions?: WdioCheckElementMethodOptions): Promise<Result>;
27
+ checkElement(element: WicElement, tag: string, checkElementOptions?: WdioCheckElementMethodOptions): Promise<Result>;
27
28
  /**
28
29
  * Compares an image of a viewport
29
30
  */
@@ -48,7 +49,7 @@ declare global {
48
49
  namespace ExpectWebdriverIO {
49
50
  interface Matchers<R, T> {
50
51
  /**
51
- * cks that if current screen matches with snapshot of baseline.
52
+ * Checks that if current screen matches with snapshot of baseline.
52
53
  * @param tag snapshot name
53
54
  * @param expectedResult either a number representing a mismatch percentage (defaults to 0) or an asymmetric matcher
54
55
  * @param options options to pass into the `checkScreen` method
@@ -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/service.d.ts CHANGED
@@ -3,7 +3,9 @@ import { BaseClass } from 'webdriver-image-comparison';
3
3
  export default class WdioImageComparisonService extends BaseClass {
4
4
  #private;
5
5
  private _browser?;
6
+ private _isNativeContext;
6
7
  constructor(options: ClassOptions);
7
- before(capabilities: WebdriverIO.Capabilities, _specs: string[], browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): void;
8
+ before(capabilities: WebdriverIO.Capabilities, _specs: string[], browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): Promise<void>;
9
+ afterCommand(commandName: string, _args: string[], result: number | string, error: any): void;
8
10
  }
9
11
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EACH,SAAS,EASZ,MAAM,4BAA4B,CAAA;AAsBnC,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,SAAS;;IAC7D,OAAO,CAAC,QAAQ,CAAC,CAAsD;gBAE3D,OAAO,EAAE,YAAY;IAGjC,MAAM,CACF,YAAY,EAAE,WAAW,CAAC,YAAY,EACtC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;CA2HpE"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EACH,SAAS,EASZ,MAAM,4BAA4B,CAAA;AAqBnC,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,SAAS;;IAC7D,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,OAAO,CAAC,gBAAgB,CAAqB;gBAEjC,OAAO,EAAE,YAAY;IAI3B,MAAM,CACR,YAAY,EAAE,WAAW,CAAC,YAAY,EACtC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IAuBjE,YAAY,CAAE,WAAW,EAAC,MAAM,EAAE,KAAK,EAAC,MAAM,EAAE,EAAE,MAAM,EAAC,MAAM,GAAC,MAAM,EAAE,KAAK,EAAC,GAAG;CA2GpF"}
package/dist/service.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import logger from '@wdio/logger';
2
2
  import { expect } from '@wdio/globals';
3
3
  import { BaseClass, checkElement, checkFullPageScreen, checkScreen, saveElement, saveFullPageScreen, saveScreen, saveTabbablePage, checkTabbablePage, } from 'webdriver-image-comparison';
4
- import { getFolders, getInstanceData } from './utils.js';
4
+ import { determineNativeContext, getFolders, getInstanceData } from './utils.js';
5
5
  import { toMatchScreenSnapshot, toMatchFullPageSnapshot, toMatchElementSnapshot, toMatchTabbablePageSnapshot } from './matcher.js';
6
6
  const log = logger('@wdio/visual-service');
7
7
  const elementCommands = { saveElement, checkElement };
@@ -15,17 +15,20 @@ const pageCommands = {
15
15
  };
16
16
  export default class WdioImageComparisonService extends BaseClass {
17
17
  _browser;
18
+ _isNativeContext;
18
19
  constructor(options) {
19
20
  super(options);
21
+ this._isNativeContext = undefined;
20
22
  }
21
- before(capabilities, _specs, browser) {
23
+ async before(capabilities, _specs, browser) {
22
24
  this._browser = browser;
25
+ this._isNativeContext = determineNativeContext(this._browser);
23
26
  if (!this._browser.isMultiremote) {
24
27
  log.info('Adding commands to global browser');
25
- this.#addCommandsToBrowser(capabilities, this._browser);
28
+ await this.#addCommandsToBrowser(this._browser);
26
29
  }
27
30
  else {
28
- this.#extendMultiremoteBrowser(capabilities);
31
+ await this.#extendMultiremoteBrowser(capabilities);
29
32
  }
30
33
  /**
31
34
  * add custom matcher for visual comparison
@@ -37,14 +40,20 @@ export default class WdioImageComparisonService extends BaseClass {
37
40
  toMatchTabbablePageSnapshot,
38
41
  });
39
42
  }
40
- #extendMultiremoteBrowser(capabilities) {
43
+ afterCommand(commandName, _args, result, error) {
44
+ // This is for the cases where in the E2E tests we switch to a WEBVIEW or back to NATIVE_APP context
45
+ if (commandName === 'getContext' && error === undefined && typeof result === 'string') {
46
+ this._isNativeContext = result.includes('NATIVE');
47
+ }
48
+ }
49
+ async #extendMultiremoteBrowser(capabilities) {
41
50
  const browser = this._browser;
42
51
  const browserNames = Object.keys(capabilities);
43
52
  log.info(`Adding commands to Multi Browser: ${browserNames.join(', ')}`);
44
53
  for (const browserName of browserNames) {
45
54
  const multiremoteBrowser = browser;
46
55
  const browserInstance = multiremoteBrowser.getInstance(browserName);
47
- this.#addCommandsToBrowser(capabilities[browserName].capabilities, browserInstance);
56
+ await this.#addCommandsToBrowser(browserInstance);
48
57
  }
49
58
  /**
50
59
  * Add all the commands to the global browser object that will execute
@@ -68,32 +77,37 @@ export default class WdioImageComparisonService extends BaseClass {
68
77
  });
69
78
  }
70
79
  }
71
- #addCommandsToBrowser(capabilities, currentBrowser) {
72
- const instanceData = getInstanceData(capabilities, currentBrowser);
73
- const folders = this.folders;
74
- const defaultOptions = this.defaultOptions;
80
+ async #addCommandsToBrowser(currentBrowser) {
81
+ const instanceData = await getInstanceData(currentBrowser);
82
+ const self = this;
75
83
  for (const [commandName, command] of Object.entries(elementCommands)) {
76
84
  log.info(`Adding element command "${commandName}" to browser object`);
77
85
  currentBrowser.addCommand(commandName, function (element, tag, elementOptions = {}) {
78
86
  return command({
79
- executor: this.execute.bind(currentBrowser),
87
+ executor: (script, ...varArgs) => {
88
+ return this.execute.bind(currentBrowser)(script, ...varArgs);
89
+ },
90
+ getElementRect: this.getElementRect.bind(currentBrowser),
80
91
  screenShot: this.takeScreenshot.bind(currentBrowser),
81
- }, instanceData, getFolders(elementOptions, folders), element, tag, {
82
- wic: defaultOptions,
92
+ }, instanceData, getFolders(elementOptions, self.folders), element, tag, {
93
+ wic: self.defaultOptions,
83
94
  method: elementOptions,
84
- });
95
+ }, self._isNativeContext);
85
96
  });
86
97
  }
87
98
  for (const [commandName, command] of Object.entries(pageCommands)) {
88
99
  log.info(`Adding element command "${commandName}" to browser object`);
89
100
  currentBrowser.addCommand(commandName, function (tag, pageOptions = {}) {
90
101
  return command({
91
- executor: this.execute.bind(currentBrowser),
102
+ executor: (script, ...varArgs) => {
103
+ return this.execute.bind(currentBrowser)(script, ...varArgs);
104
+ },
105
+ getElementRect: this.getElementRect.bind(currentBrowser),
92
106
  screenShot: this.takeScreenshot.bind(currentBrowser),
93
- }, instanceData, getFolders(pageOptions, folders), tag, {
94
- wic: defaultOptions,
107
+ }, instanceData, getFolders(pageOptions, self.folders), tag, {
108
+ wic: self.defaultOptions,
95
109
  method: pageOptions,
96
- });
110
+ }, self._isNativeContext);
97
111
  });
98
112
  }
99
113
  }
package/dist/utils.d.ts CHANGED
@@ -7,13 +7,31 @@ import type { Folders, InstanceData, CheckScreenMethodOptions, SaveScreenMethodO
7
7
  */
8
8
  type getFolderMethodOptions = CheckElementMethodOptions | CheckFullPageMethodOptions | CheckScreenMethodOptions | SaveElementMethodOptions | SaveFullPageMethodOptions | SaveScreenMethodOptions;
9
9
  export declare function getFolders(methodOptions: getFolderMethodOptions, folders: Folders): Folders;
10
+ /**
11
+ * Get the size of a screenshot in pixels without the device pixel ratio
12
+ */
13
+ export declare function getScreenshotSize(screenshot: string, devicePixelRation?: number): {
14
+ height: number;
15
+ width: number;
16
+ };
17
+ /**
18
+ * Get the device pixel ratio
19
+ */
20
+ export declare function getDevicePixelRatio(screenshot: string, deviceScreenSize: {
21
+ height: number;
22
+ width: number;
23
+ }): number;
10
24
  /**
11
25
  * Get the instance data
12
26
  */
13
- export declare function getInstanceData(capabilities: WebdriverIO.Capabilities, currentBrowser: WebdriverIO.Browser): InstanceData;
27
+ export declare function getInstanceData(currentBrowser: WebdriverIO.Browser): Promise<InstanceData>;
14
28
  /**
15
- * traverse up the scope chain until browser element was reached
29
+ * Traverse up the scope chain until browser element was reached
16
30
  */
17
31
  export declare function getBrowserObject(elem: WebdriverIO.Element | WebdriverIO.Browser): WebdriverIO.Browser;
32
+ /**
33
+ * 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
+ export declare function determineNativeContext(driver: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): boolean;
18
36
  export {};
19
37
  //# 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;AAOnC;;;;;GAKG;AACH,KAAK,sBAAsB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,yBAAyB,GACzB,uBAAuB,CAAC;AAC9B,wBAAgB,UAAU,CACtB,aAAa,EAAE,sBAAsB,EACrC,OAAO,EAAE,OAAO,GACjB,OAAO,CAMT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC3B,YAAY,EAAE,WAAW,CAAC,YAAY,EACtC,cAAc,EAAE,WAAW,CAAC,OAAO,GACpC,YAAY,CAyDd;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAE,IAAI,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,CAGtG"}
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;AAOnC;;;;;GAKG;AACH,KAAK,sBAAsB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,yBAAyB,GACzB,uBAAuB,CAAC;AAC9B,wBAAgB,UAAU,CACtB,aAAa,EAAE,sBAAsB,EACrC,OAAO,EAAE,OAAO,GACjB,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,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,OAAO,CAQT"}
package/dist/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- /// <reference types="webdriverio" />
1
+ import { IOS_OFFSETS } from 'webdriver-image-comparison';
2
2
  export function getFolders(methodOptions, folders) {
3
3
  return {
4
4
  actualFolder: methodOptions.actualFolder ?? folders.actualFolder,
@@ -6,40 +6,135 @@ export function getFolders(methodOptions, folders) {
6
6
  diffFolder: methodOptions.diffFolder ?? folders.diffFolder,
7
7
  };
8
8
  }
9
+ /**
10
+ * Get the size of a screenshot in pixels without the device pixel ratio
11
+ */
12
+ export function getScreenshotSize(screenshot, devicePixelRation = 1) {
13
+ return {
14
+ height: Buffer.from(screenshot, 'base64').readUInt32BE(20) / devicePixelRation,
15
+ width: Buffer.from(screenshot, 'base64').readUInt32BE(16) / devicePixelRation,
16
+ };
17
+ }
18
+ /**
19
+ * Get the device pixel ratio
20
+ */
21
+ export function getDevicePixelRatio(screenshot, deviceScreenSize) {
22
+ const screenshotSize = getScreenshotSize(screenshot);
23
+ const devicePixelRatio = Math.round(screenshotSize.width / deviceScreenSize.width) === Math.round(screenshotSize.height / deviceScreenSize.height)
24
+ ? Math.round(screenshotSize.width / deviceScreenSize.width)
25
+ : Math.round(screenshotSize.height / deviceScreenSize.width);
26
+ return Math.round(devicePixelRatio);
27
+ }
28
+ /**
29
+ * Get the mobile instance data
30
+ */
31
+ async function getMobileInstanceData({ currentBrowser, isAndroid, isMobile }) {
32
+ const deviceScreenSize = {
33
+ height: 0,
34
+ width: 0,
35
+ };
36
+ const devicePlatformRect = {
37
+ statusBar: { height: 0, x: 0, width: 0, y: 0 },
38
+ homeBar: { height: 0, x: 0, width: 0, y: 0 },
39
+ };
40
+ let devicePixelRatio = 1;
41
+ if (isMobile) {
42
+ const currentDriverCapabilities = currentBrowser.capabilities;
43
+ const { height, width } = await currentBrowser.getWindowSize();
44
+ deviceScreenSize.height = height;
45
+ deviceScreenSize.width = width;
46
+ // @TODO: This is al based on PORTRAIT mode
47
+ if (isAndroid && currentDriverCapabilities) {
48
+ // We use a few `@ts-ignore` here because `pixelRatio` and `statBarHeight`
49
+ // are returned by the driver, and not recognized by the types because they are not requested
50
+ // @ts-ignore
51
+ if (currentDriverCapabilities?.pixelRatio !== undefined) {
52
+ // @ts-ignore
53
+ devicePixelRatio = currentDriverCapabilities?.pixelRatio;
54
+ }
55
+ // @ts-ignore
56
+ if (currentDriverCapabilities?.statBarHeight !== undefined) {
57
+ // @ts-ignore
58
+ devicePlatformRect.statusBar.height = currentDriverCapabilities?.statBarHeight;
59
+ devicePlatformRect.statusBar.width = width;
60
+ }
61
+ }
62
+ else {
63
+ // This is to already determine the device pixel ratio if it's not set in the capabilities
64
+ const base64Image = await currentBrowser.takeScreenshot();
65
+ devicePixelRatio = getDevicePixelRatio(base64Image, deviceScreenSize);
66
+ const isIphone = width < 1024 && height < 1024;
67
+ const deviceType = isIphone ? 'IPHONE' : 'IPAD';
68
+ const defaultPortraitHeight = isIphone ? 667 : 1024;
69
+ const portraitHeight = width > height ? width : height;
70
+ const offsetPortraitHeight = Object.keys(IOS_OFFSETS[deviceType]).indexOf(portraitHeight.toString()) > -1 ? portraitHeight : defaultPortraitHeight;
71
+ const currentOffsets = IOS_OFFSETS[deviceType][offsetPortraitHeight].PORTRAIT;
72
+ // NOTE: The values for iOS are based on CSS pixels, so we need to multiply them with the devicePixelRatio,
73
+ // This will NOT be done here but in a central place
74
+ devicePlatformRect.statusBar = {
75
+ y: 0,
76
+ x: 0,
77
+ width,
78
+ height: currentOffsets.STATUS_BAR,
79
+ };
80
+ devicePlatformRect.homeBar = currentOffsets.HOME_BAR;
81
+ }
82
+ }
83
+ return {
84
+ devicePixelRatio,
85
+ devicePlatformRect,
86
+ deviceScreenSize,
87
+ };
88
+ }
9
89
  /**
10
90
  * Get the instance data
11
91
  */
12
- export function getInstanceData(capabilities, currentBrowser) {
13
- const currentCapabilities = currentBrowser.requestedCapabilities.alwaysMatch
14
- ? currentBrowser.requestedCapabilities.alwaysMatch
15
- : currentBrowser.requestedCapabilities;
16
- const browserName = (capabilities.browserName ||
17
- currentCapabilities.browserName ||
18
- 'browserName-not-known').toLowerCase();
19
- const browserVersion = (capabilities.browserVersion ||
20
- currentCapabilities.browserVersion ||
21
- 'not-known').toLowerCase();
22
- const logName = 'wdio-ics:options' in capabilities
23
- ? capabilities['wdio-ics:options']?.logName ??
24
- ''
92
+ export async function getInstanceData(currentBrowser) {
93
+ const NOT_KNOWN = 'not-known';
94
+ const { capabilities: currentCapabilities, requestedCapabilities } = currentBrowser;
95
+ const { browserName: rawBrowserName = NOT_KNOWN, browserVersion: rawBrowserVersion = NOT_KNOWN, platformName: rawPlatformName = NOT_KNOWN, } = currentCapabilities;
96
+ // Generic data
97
+ const browserName = rawBrowserName === '' ? NOT_KNOWN : rawBrowserName.toLowerCase();
98
+ const browserVersion = rawBrowserVersion === '' ? NOT_KNOWN : rawBrowserVersion.toLowerCase();
99
+ let devicePixelRatio = 1;
100
+ const platformName = rawPlatformName === '' ? NOT_KNOWN : rawPlatformName.toLowerCase();
101
+ const logName = 'wdio-ics:options' in requestedCapabilities
102
+ ? requestedCapabilities['wdio-ics:options']?.logName ?? ''
25
103
  : '';
26
- const name = 'wdio-ics:options' in capabilities
27
- ? capabilities['wdio-ics:options']?.name ?? ''
104
+ const name = 'wdio-ics:options' in requestedCapabilities
105
+ ? requestedCapabilities['wdio-ics:options']?.name ?? ''
28
106
  : '';
29
- // For mobile
30
- const platformName = (capabilities.platformName ||
31
- currentCapabilities.platformName ||
32
- 'not-known').toLowerCase();
33
- const platformVersion = (capabilities['appium:platformVersion'] ||
34
- currentCapabilities['appium:platformVersion'] ||
35
- 'not-known').toLowerCase();
36
- const deviceName = (capabilities['appium:deviceName'] || '').toLowerCase();
37
- const nativeWebScreenshot = !!(capabilities['appium:nativeWebScreenshot'] ||
38
- currentCapabilities['appium:nativeWebScreenshot']);
107
+ // Mobile data
108
+ const { isAndroid, isIOS, isMobile } = currentBrowser;
109
+ const {
110
+ // We use a few `@ts-ignore` here because this is returned by the driver
111
+ // and not recognized by the types because they are not requested
112
+ // @ts-ignore
113
+ app: rawApp = NOT_KNOWN,
114
+ // @ts-ignore
115
+ deviceName: rawDeviceName = NOT_KNOWN,
116
+ // @ts-ignore
117
+ platformVersion: rawPlatformVersion = NOT_KNOWN, } = currentCapabilities;
118
+ const { 'appium:deviceName': requestedDeviceName } = requestedCapabilities;
119
+ const appName = rawApp !== NOT_KNOWN
120
+ ? rawApp.replace(/\\/g, '/').split('/').pop().replace(/[^a-zA-Z0-9]/g, '_')
121
+ : NOT_KNOWN;
122
+ const deviceName = (requestedDeviceName || rawDeviceName || '').toLowerCase();
123
+ const nativeWebScreenshot = !!(requestedCapabilities['appium:nativeWebScreenshot']);
124
+ const platformVersion = (rawPlatformVersion === undefined || rawPlatformVersion === '') ? NOT_KNOWN : rawPlatformVersion.toLowerCase();
125
+ const { devicePixelRatio: mobileDevicePixelRatio, devicePlatformRect, deviceScreenSize, } = await getMobileInstanceData({ currentBrowser, isAndroid, isMobile });
126
+ devicePixelRatio = isMobile ? mobileDevicePixelRatio : devicePixelRatio;
39
127
  return {
128
+ appName,
40
129
  browserName,
41
130
  browserVersion,
42
131
  deviceName,
132
+ devicePixelRatio,
133
+ devicePlatformRect,
134
+ deviceScreenSize,
135
+ isAndroid,
136
+ isIOS,
137
+ isMobile,
43
138
  logName,
44
139
  name,
45
140
  nativeWebScreenshot,
@@ -48,9 +143,20 @@ export function getInstanceData(capabilities, currentBrowser) {
48
143
  };
49
144
  }
50
145
  /**
51
- * traverse up the scope chain until browser element was reached
146
+ * Traverse up the scope chain until browser element was reached
52
147
  */
53
148
  export function getBrowserObject(elem) {
54
149
  const elemObject = elem;
55
150
  return elemObject.parent ? getBrowserObject(elemObject.parent) : elem;
56
151
  }
152
+ /**
153
+ * 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
154
+ */
155
+ export function determineNativeContext(driver) {
156
+ if (driver.isMobile) {
157
+ return !!driver.requestedCapabilities?.browserName === false
158
+ && driver.requestedCapabilities?.['appium:app'] !== undefined
159
+ && driver.requestedCapabilities?.['appium:autoWebview'] !== true;
160
+ }
161
+ return false;
162
+ }
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": "1.0.0",
5
+ "version": "2.0.0",
6
6
  "license": "MIT",
7
7
  "homepage": "https://webdriver.io/docs/visual-testing",
8
8
  "repository": {
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@wdio/logger": "^8.24.12",
24
24
  "@wdio/types": "^8.26.2",
25
- "webdriver-image-comparison": "^3.0.0"
25
+ "webdriver-image-comparison": "^4.0.0"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "run-s clean build:*",