@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.
- package/CHANGELOG.md +158 -1
- package/dist/contextManager.d.ts +15 -0
- package/dist/contextManager.d.ts.map +1 -0
- package/dist/contextManager.js +71 -0
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/service.d.ts +4 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +209 -148
- package/dist/storybook/Types.d.ts +17 -4
- package/dist/storybook/Types.d.ts.map +1 -1
- package/dist/storybook/launcher.d.ts.map +1 -1
- package/dist/storybook/launcher.js +23 -0
- package/dist/storybook/utils.d.ts +4 -4
- package/dist/storybook/utils.d.ts.map +1 -1
- package/dist/storybook/utils.js +35 -13
- package/dist/types.d.ts +33 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +13 -20
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +87 -89
- package/dist/wrapWithContext.d.ts +8 -0
- package/dist/wrapWithContext.d.ts.map +1 -0
- package/dist/wrapWithContext.js +21 -0
- package/package.json +5 -5
package/dist/storybook/utils.js
CHANGED
|
@@ -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
|
-
|
|
84
|
-
|
|
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
|
|
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
|
|
134
|
-
|
|
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(
|
|
145
|
-
: `await expect(browser).toMatchScreenSnapshot('${id}', ${JSON.stringify(
|
|
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
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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)[];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,YAAY,EACf,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;
|
|
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,
|
|
2
|
-
import type {
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
45
|
+
* Check if the current browser supports isBidi screenshots
|
|
47
46
|
*/
|
|
48
|
-
export declare function
|
|
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
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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,
|
|
33
|
-
const
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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,
|
|
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 ?
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
y: 0,
|
|
105
|
+
deviceRectangles.statusBar = {
|
|
77
106
|
x: 0,
|
|
78
|
-
|
|
107
|
+
y: 0,
|
|
108
|
+
width: deviceRectangles.screenSize.width,
|
|
79
109
|
height: currentOffsets.STATUS_BAR,
|
|
80
110
|
};
|
|
81
|
-
|
|
111
|
+
deviceRectangles.homeBar = currentOffsets.HOME_BAR;
|
|
82
112
|
}
|
|
83
113
|
}
|
|
84
114
|
return {
|
|
85
115
|
devicePixelRatio,
|
|
86
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
return
|
|
245
|
-
|
|
246
|
-
|
|
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": "
|
|
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.
|
|
23
|
+
"@wdio/globals": "^9.12.6",
|
|
24
24
|
"@wdio/logger": "^9.4.4",
|
|
25
|
-
"@wdio/types": "^9.
|
|
26
|
-
"
|
|
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",
|