@argos-ci/playwright 3.8.0 → 3.9.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/dist/index.cjs +11 -15
- package/dist/index.d.ts +22 -9
- package/dist/index.js +326 -0
- package/dist/reporter.d.ts +5 -16
- package/dist/reporter.js +342 -0
- package/package.json +13 -14
- package/dist/index.mjs +0 -334
- package/dist/reporter.mjs +0 -344
package/dist/index.mjs
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { mkdir } from 'node:fs/promises';
|
|
2
|
-
import { relative, resolve, dirname } from 'node:path';
|
|
3
|
-
import { resolveViewport, getGlobalScript } from '@argos-ci/browser';
|
|
4
|
-
import { getGitRepositoryPath, readVersionFromPackage, getScreenshotName, validateThreshold, writeMetadata, getMetadataPath } from '@argos-ci/util';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
7
|
-
import { createHash } from 'node:crypto';
|
|
8
|
-
|
|
9
|
-
function getAttachmentName(name, type) {
|
|
10
|
-
return `argos/${type}___${name}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const require$1 = createRequire(import.meta.url);
|
|
14
|
-
/**
|
|
15
|
-
* Try to resolve a package.
|
|
16
|
-
*/ function tryResolve(pkg) {
|
|
17
|
-
try {
|
|
18
|
-
return require$1.resolve(pkg);
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Private metadata config storage.
|
|
25
|
-
* Used to inject the metadata from other SDKs like @argos-ci/storybook.
|
|
26
|
-
*/ const metadataConfigStorage = new AsyncLocalStorage();
|
|
27
|
-
/**
|
|
28
|
-
* Set the metadata config.
|
|
29
|
-
*/ async function setMetadataConfig(metadata) {
|
|
30
|
-
metadataConfigStorage.enterWith(metadata);
|
|
31
|
-
}
|
|
32
|
-
const DEFAULT_PLAYWRIGHT_LIBRARIES = [
|
|
33
|
-
"@playwright/test",
|
|
34
|
-
"playwright",
|
|
35
|
-
"playwright-core"
|
|
36
|
-
];
|
|
37
|
-
/**
|
|
38
|
-
* Get the name and version of the automation library.
|
|
39
|
-
*/ async function getAutomationLibraryMetadata() {
|
|
40
|
-
const metadataConfig = metadataConfigStorage.getStore();
|
|
41
|
-
const libraries = metadataConfig?.playwrightLibraries ?? DEFAULT_PLAYWRIGHT_LIBRARIES;
|
|
42
|
-
for (const name of libraries){
|
|
43
|
-
const pkgPath = tryResolve(`${name}/package.json`);
|
|
44
|
-
if (pkgPath) {
|
|
45
|
-
const version = await readVersionFromPackage(pkgPath);
|
|
46
|
-
return {
|
|
47
|
-
version,
|
|
48
|
-
name
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
throw new Error(`Unable to find any of the following packages: ${libraries.join(", ")}`);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Get the version of the Argos Playwright SDK.
|
|
56
|
-
*/ async function getArgosPlaywrightVersion() {
|
|
57
|
-
const pkgPath = require$1.resolve("@argos-ci/playwright/package.json");
|
|
58
|
-
return readVersionFromPackage(pkgPath);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get the name and version of the SDK.
|
|
62
|
-
*/ async function getSdkMetadata() {
|
|
63
|
-
// Get the SDK metadata from the async local storage.
|
|
64
|
-
const metadataConfig = metadataConfigStorage.getStore();
|
|
65
|
-
if (metadataConfig) {
|
|
66
|
-
return metadataConfig.sdk;
|
|
67
|
-
}
|
|
68
|
-
// Get the SDK metadata from the current SDK.
|
|
69
|
-
const argosPlaywrightVersion = await getArgosPlaywrightVersion();
|
|
70
|
-
return {
|
|
71
|
-
name: "@argos-ci/playwright",
|
|
72
|
-
version: argosPlaywrightVersion
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Get the metadata of the automation library and the SDK.
|
|
77
|
-
*/ async function getLibraryMetadata() {
|
|
78
|
-
const [automationLibrary, sdk] = await Promise.all([
|
|
79
|
-
getAutomationLibraryMetadata(),
|
|
80
|
-
getSdkMetadata()
|
|
81
|
-
]);
|
|
82
|
-
return {
|
|
83
|
-
automationLibrary,
|
|
84
|
-
sdk
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Get the metadata of the test.
|
|
89
|
-
*/ async function getTestMetadataFromTestInfo(testInfo) {
|
|
90
|
-
const repositoryPath = await getGitRepositoryPath();
|
|
91
|
-
const testMetadata = {
|
|
92
|
-
id: testInfo.testId,
|
|
93
|
-
title: testInfo.title,
|
|
94
|
-
titlePath: testInfo.titlePath,
|
|
95
|
-
retry: testInfo.retry,
|
|
96
|
-
retries: testInfo.project.retries,
|
|
97
|
-
repeat: testInfo.repeatEachIndex,
|
|
98
|
-
location: {
|
|
99
|
-
file: repositoryPath ? relative(repositoryPath, testInfo.file) : testInfo.file,
|
|
100
|
-
line: testInfo.line,
|
|
101
|
-
column: testInfo.column
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
return testMetadata;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const require = createRequire(import.meta.url);
|
|
108
|
-
function checkIsUsingArgosReporter(testInfo) {
|
|
109
|
-
const reporterPath = require.resolve("@argos-ci/playwright/reporter");
|
|
110
|
-
return testInfo.config.reporter.some((reporter)=>reporter[0].includes("@argos-ci/playwright/reporter") || reporter[0] === reporterPath);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const DEFAULT_SCREENSHOT_ROOT = "./screenshots";
|
|
114
|
-
/**
|
|
115
|
-
* Inject Argos script into the page.
|
|
116
|
-
*/ async function injectArgos(page) {
|
|
117
|
-
const injected = await page.evaluate(()=>typeof window.__ARGOS__ !== "undefined");
|
|
118
|
-
if (!injected) {
|
|
119
|
-
await page.addScriptTag({
|
|
120
|
-
content: getGlobalScript()
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Get test info from the Playwright test.
|
|
126
|
-
*/ async function getTestInfo() {
|
|
127
|
-
try {
|
|
128
|
-
const { test } = await import('@playwright/test');
|
|
129
|
-
return test.info();
|
|
130
|
-
} catch (error) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Get the viewport size.
|
|
136
|
-
*/ function getViewportSize(page) {
|
|
137
|
-
const viewportSize = page.viewportSize();
|
|
138
|
-
if (!viewportSize) {
|
|
139
|
-
throw new Error("Can't take screenshots without a viewport.");
|
|
140
|
-
}
|
|
141
|
-
return viewportSize;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Sets the viewport size and waits for the visual viewport to match the specified dimensions.
|
|
145
|
-
* @returns A promise that resolves when the viewport size has been successfully set and matched.
|
|
146
|
-
*/ async function setViewportSize(page, viewportSize) {
|
|
147
|
-
await page.setViewportSize(viewportSize);
|
|
148
|
-
await page.waitForFunction(({ width, height })=>window.innerWidth === width && window.innerHeight === height, {
|
|
149
|
-
width: viewportSize.width,
|
|
150
|
-
height: viewportSize.height
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Setup Argos for the screenshot process.
|
|
155
|
-
* @returns A function to teardown Argos.
|
|
156
|
-
*/ async function setup(page, options) {
|
|
157
|
-
const { disableHover = true, fullPage, argosCSS } = options;
|
|
158
|
-
await page.evaluate(({ fullPage, argosCSS })=>window.__ARGOS__.setup({
|
|
159
|
-
fullPage,
|
|
160
|
-
argosCSS
|
|
161
|
-
}), {
|
|
162
|
-
fullPage,
|
|
163
|
-
argosCSS
|
|
164
|
-
});
|
|
165
|
-
if (disableHover) {
|
|
166
|
-
await page.mouse.move(0, 0);
|
|
167
|
-
}
|
|
168
|
-
return async ()=>{
|
|
169
|
-
await page.evaluate(({ fullPage, argosCSS })=>window.__ARGOS__.teardown({
|
|
170
|
-
fullPage,
|
|
171
|
-
argosCSS
|
|
172
|
-
}), {
|
|
173
|
-
fullPage,
|
|
174
|
-
argosCSS
|
|
175
|
-
});
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Get the screenshot names based on the test info.
|
|
180
|
-
*/ function getScreenshotNames(name, testInfo) {
|
|
181
|
-
if (testInfo) {
|
|
182
|
-
const projectName = `${testInfo.project.name}/${name}`;
|
|
183
|
-
if (testInfo.repeatEachIndex > 0) {
|
|
184
|
-
return {
|
|
185
|
-
name: `${projectName} repeat-${testInfo.repeatEachIndex}`,
|
|
186
|
-
baseName: projectName
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
name: projectName,
|
|
191
|
-
baseName: null
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
name,
|
|
196
|
-
baseName: null
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Stabilize the UI and takes a screenshot of the application under test.
|
|
201
|
-
*
|
|
202
|
-
* @example
|
|
203
|
-
* argosScreenshot(page, "my-screenshot")
|
|
204
|
-
* @see https://argos-ci.com/docs/playwright#api-overview
|
|
205
|
-
*/ async function argosScreenshot(/**
|
|
206
|
-
* Playwright `page` object.
|
|
207
|
-
*/ page, /**
|
|
208
|
-
* Name of the screenshot. Must be unique.
|
|
209
|
-
*/ name, /**
|
|
210
|
-
* Options for the screenshot.
|
|
211
|
-
*/ options = {}) {
|
|
212
|
-
const { element, has, hasText, viewports, argosCSS, root = DEFAULT_SCREENSHOT_ROOT, ...playwrightOptions } = options;
|
|
213
|
-
if (!page) {
|
|
214
|
-
throw new Error("A Playwright `page` object is required.");
|
|
215
|
-
}
|
|
216
|
-
if (!name) {
|
|
217
|
-
throw new Error("The `name` argument is required.");
|
|
218
|
-
}
|
|
219
|
-
const handle = typeof element === "string" ? page.locator(element, {
|
|
220
|
-
has,
|
|
221
|
-
hasText
|
|
222
|
-
}) : element ?? page;
|
|
223
|
-
const testInfo = await getTestInfo();
|
|
224
|
-
const useArgosReporter = Boolean(testInfo && checkIsUsingArgosReporter(testInfo));
|
|
225
|
-
await Promise.all([
|
|
226
|
-
// Create the screenshot folder if it doesn't exist
|
|
227
|
-
useArgosReporter ? null : mkdir(root, {
|
|
228
|
-
recursive: true
|
|
229
|
-
}),
|
|
230
|
-
// Inject Argos script into the page
|
|
231
|
-
injectArgos(page)
|
|
232
|
-
]);
|
|
233
|
-
const originalViewportSize = getViewportSize(page);
|
|
234
|
-
const fullPage = options.fullPage !== undefined ? options.fullPage : handle === page;
|
|
235
|
-
const teardown = await setup(page, options);
|
|
236
|
-
const collectMetadata = async (testInfo)=>{
|
|
237
|
-
const [colorScheme, mediaType, libMetadata, testMetadata] = await Promise.all([
|
|
238
|
-
page.evaluate(()=>window.__ARGOS__.getColorScheme()),
|
|
239
|
-
page.evaluate(()=>window.__ARGOS__.getMediaType()),
|
|
240
|
-
getLibraryMetadata(),
|
|
241
|
-
testInfo ? getTestMetadataFromTestInfo(testInfo) : null
|
|
242
|
-
]);
|
|
243
|
-
const viewportSize = getViewportSize(page);
|
|
244
|
-
const browser = page.context().browser();
|
|
245
|
-
if (!browser) {
|
|
246
|
-
throw new Error("Can't take screenshots without a browser.");
|
|
247
|
-
}
|
|
248
|
-
const browserName = browser.browserType().name();
|
|
249
|
-
const browserVersion = browser.version();
|
|
250
|
-
const metadata = {
|
|
251
|
-
url: page.url(),
|
|
252
|
-
viewport: viewportSize,
|
|
253
|
-
colorScheme,
|
|
254
|
-
mediaType,
|
|
255
|
-
test: testMetadata,
|
|
256
|
-
browser: {
|
|
257
|
-
name: browserName,
|
|
258
|
-
version: browserVersion
|
|
259
|
-
},
|
|
260
|
-
...libMetadata
|
|
261
|
-
};
|
|
262
|
-
return metadata;
|
|
263
|
-
};
|
|
264
|
-
const stabilizeAndScreenshot = async (name)=>{
|
|
265
|
-
await page.waitForFunction(()=>window.__ARGOS__.waitForStability());
|
|
266
|
-
const names = getScreenshotNames(name, testInfo);
|
|
267
|
-
const metadata = await collectMetadata(testInfo);
|
|
268
|
-
metadata.transient = {};
|
|
269
|
-
if (options.threshold !== undefined) {
|
|
270
|
-
validateThreshold(options.threshold);
|
|
271
|
-
metadata.transient.threshold = options.threshold;
|
|
272
|
-
}
|
|
273
|
-
if (names.baseName) {
|
|
274
|
-
metadata.transient.baseName = `${names.baseName}.png`;
|
|
275
|
-
}
|
|
276
|
-
const screenshotPath = useArgosReporter && testInfo ? testInfo.outputPath("argos", `${names.name}.png`) : resolve(root, `${names.name}.png`);
|
|
277
|
-
const dir = dirname(screenshotPath);
|
|
278
|
-
if (dir !== root) {
|
|
279
|
-
await mkdir(dirname(screenshotPath), {
|
|
280
|
-
recursive: true
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
await Promise.all([
|
|
284
|
-
handle.screenshot({
|
|
285
|
-
path: screenshotPath,
|
|
286
|
-
type: "png",
|
|
287
|
-
fullPage,
|
|
288
|
-
mask: [
|
|
289
|
-
page.locator('[data-visual-test="blackout"]')
|
|
290
|
-
],
|
|
291
|
-
animations: "disabled",
|
|
292
|
-
...playwrightOptions
|
|
293
|
-
}),
|
|
294
|
-
writeMetadata(screenshotPath, metadata)
|
|
295
|
-
]);
|
|
296
|
-
if (useArgosReporter && testInfo) {
|
|
297
|
-
await Promise.all([
|
|
298
|
-
testInfo.attach(getAttachmentName(names.name, "metadata"), {
|
|
299
|
-
path: getMetadataPath(screenshotPath),
|
|
300
|
-
contentType: "application/json"
|
|
301
|
-
}),
|
|
302
|
-
testInfo.attach(getAttachmentName(names.name, "screenshot"), {
|
|
303
|
-
path: screenshotPath,
|
|
304
|
-
contentType: "image/png"
|
|
305
|
-
})
|
|
306
|
-
]);
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
// If no viewports are specified, take a single screenshot
|
|
310
|
-
if (viewports) {
|
|
311
|
-
// Take screenshots for each viewport
|
|
312
|
-
for (const viewport of viewports){
|
|
313
|
-
const viewportSize = resolveViewport(viewport);
|
|
314
|
-
await setViewportSize(page, viewportSize);
|
|
315
|
-
await stabilizeAndScreenshot(getScreenshotName(name, {
|
|
316
|
-
viewportWidth: viewportSize.width
|
|
317
|
-
}));
|
|
318
|
-
}
|
|
319
|
-
// Restore the original viewport size
|
|
320
|
-
await setViewportSize(page, originalViewportSize);
|
|
321
|
-
} else {
|
|
322
|
-
await stabilizeAndScreenshot(name);
|
|
323
|
-
}
|
|
324
|
-
await teardown();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Get the CSP script hash.
|
|
329
|
-
*/ async function getCSPScriptHash() {
|
|
330
|
-
const hash = createHash("sha256").update(getGlobalScript()).digest("base64");
|
|
331
|
-
return `'sha256-${hash}'`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export { setMetadataConfig as DO_NOT_USE_setMetadataConfig, argosScreenshot, getCSPScriptHash };
|
package/dist/reporter.mjs
DELETED
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { upload, readConfig } from '@argos-ci/core';
|
|
3
|
-
import { randomBytes } from 'node:crypto';
|
|
4
|
-
import { writeFile, copyFile, readdir, mkdir } from 'node:fs/promises';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { relative, dirname, join } from 'node:path';
|
|
7
|
-
import { getGitRepositoryPath, readVersionFromPackage } from '@argos-ci/util';
|
|
8
|
-
import { createRequire } from 'node:module';
|
|
9
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
10
|
-
import createDebug from 'debug';
|
|
11
|
-
|
|
12
|
-
function getOriginalAttachmentName(name) {
|
|
13
|
-
return name.replace(/^argos\/[^/]+___/, "");
|
|
14
|
-
}
|
|
15
|
-
function getAttachmentFilename(name) {
|
|
16
|
-
if (name.startsWith("argos/screenshot")) {
|
|
17
|
-
return `${getOriginalAttachmentName(name)}.png`;
|
|
18
|
-
}
|
|
19
|
-
if (name.startsWith("argos/metadata")) {
|
|
20
|
-
return `${getOriginalAttachmentName(name)}.png.argos.json`;
|
|
21
|
-
}
|
|
22
|
-
throw new Error(`Unknown attachment name: ${name}`);
|
|
23
|
-
}
|
|
24
|
-
function checkIsTrace(attachment) {
|
|
25
|
-
return attachment.name === "trace" && attachment.contentType === "application/zip" && Boolean(attachment.path);
|
|
26
|
-
}
|
|
27
|
-
function checkIsArgosScreenshot(attachment) {
|
|
28
|
-
return attachment.name.startsWith("argos/") && attachment.contentType === "image/png" && Boolean(attachment.path);
|
|
29
|
-
}
|
|
30
|
-
function checkIsArgosScreenshotMetadata(attachment) {
|
|
31
|
-
return attachment.name.startsWith("argos/") && attachment.contentType === "application/json" && Boolean(attachment.path);
|
|
32
|
-
}
|
|
33
|
-
function checkIsAutomaticScreenshot(attachment) {
|
|
34
|
-
return attachment.name === "screenshot" && attachment.contentType === "image/png" && Boolean(attachment.path);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const require = createRequire(import.meta.url);
|
|
38
|
-
/**
|
|
39
|
-
* Try to resolve a package.
|
|
40
|
-
*/ function tryResolve(pkg) {
|
|
41
|
-
try {
|
|
42
|
-
return require.resolve(pkg);
|
|
43
|
-
} catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Private metadata config storage.
|
|
49
|
-
* Used to inject the metadata from other SDKs like @argos-ci/storybook.
|
|
50
|
-
*/ const metadataConfigStorage = new AsyncLocalStorage();
|
|
51
|
-
const DEFAULT_PLAYWRIGHT_LIBRARIES = [
|
|
52
|
-
"@playwright/test",
|
|
53
|
-
"playwright",
|
|
54
|
-
"playwright-core"
|
|
55
|
-
];
|
|
56
|
-
/**
|
|
57
|
-
* Get the name and version of the automation library.
|
|
58
|
-
*/ async function getAutomationLibraryMetadata() {
|
|
59
|
-
const metadataConfig = metadataConfigStorage.getStore();
|
|
60
|
-
const libraries = metadataConfig?.playwrightLibraries ?? DEFAULT_PLAYWRIGHT_LIBRARIES;
|
|
61
|
-
for (const name of libraries){
|
|
62
|
-
const pkgPath = tryResolve(`${name}/package.json`);
|
|
63
|
-
if (pkgPath) {
|
|
64
|
-
const version = await readVersionFromPackage(pkgPath);
|
|
65
|
-
return {
|
|
66
|
-
version,
|
|
67
|
-
name
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
throw new Error(`Unable to find any of the following packages: ${libraries.join(", ")}`);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Get the version of the Argos Playwright SDK.
|
|
75
|
-
*/ async function getArgosPlaywrightVersion() {
|
|
76
|
-
const pkgPath = require.resolve("@argos-ci/playwright/package.json");
|
|
77
|
-
return readVersionFromPackage(pkgPath);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Get the name and version of the SDK.
|
|
81
|
-
*/ async function getSdkMetadata() {
|
|
82
|
-
// Get the SDK metadata from the async local storage.
|
|
83
|
-
const metadataConfig = metadataConfigStorage.getStore();
|
|
84
|
-
if (metadataConfig) {
|
|
85
|
-
return metadataConfig.sdk;
|
|
86
|
-
}
|
|
87
|
-
// Get the SDK metadata from the current SDK.
|
|
88
|
-
const argosPlaywrightVersion = await getArgosPlaywrightVersion();
|
|
89
|
-
return {
|
|
90
|
-
name: "@argos-ci/playwright",
|
|
91
|
-
version: argosPlaywrightVersion
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Get the metadata of the automation library and the SDK.
|
|
96
|
-
*/ async function getLibraryMetadata() {
|
|
97
|
-
const [automationLibrary, sdk] = await Promise.all([
|
|
98
|
-
getAutomationLibraryMetadata(),
|
|
99
|
-
getSdkMetadata()
|
|
100
|
-
]);
|
|
101
|
-
return {
|
|
102
|
-
automationLibrary,
|
|
103
|
-
sdk
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
async function getTestMetadataFromTestCase(testCase, testResult) {
|
|
107
|
-
const repositoryPath = await getGitRepositoryPath();
|
|
108
|
-
const testMetadata = {
|
|
109
|
-
title: testCase.title,
|
|
110
|
-
titlePath: testCase.titlePath(),
|
|
111
|
-
retry: testResult.retry,
|
|
112
|
-
retries: testCase.retries,
|
|
113
|
-
repeat: testCase.repeatEachIndex,
|
|
114
|
-
location: {
|
|
115
|
-
file: repositoryPath ? relative(repositoryPath, testCase.location.file) : testCase.location.file,
|
|
116
|
-
line: testCase.location.line,
|
|
117
|
-
column: testCase.location.column
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
return testMetadata;
|
|
121
|
-
}
|
|
122
|
-
async function getMetadataFromTestCase(testCase, testResult) {
|
|
123
|
-
const [libMetadata, testMetadata] = await Promise.all([
|
|
124
|
-
getLibraryMetadata(),
|
|
125
|
-
getTestMetadataFromTestCase(testCase, testResult)
|
|
126
|
-
]);
|
|
127
|
-
const metadata = {
|
|
128
|
-
test: testMetadata,
|
|
129
|
-
...libMetadata
|
|
130
|
-
};
|
|
131
|
-
return metadata;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const KEY = "@argos-ci/playwright";
|
|
135
|
-
const debug = createDebug(KEY);
|
|
136
|
-
|
|
137
|
-
const createDirectoryPromises = new Map();
|
|
138
|
-
/**
|
|
139
|
-
* Create a directory if it doesn't exist.
|
|
140
|
-
*/ async function createDirectory(pathname) {
|
|
141
|
-
let promise = createDirectoryPromises.get(pathname);
|
|
142
|
-
if (promise) {
|
|
143
|
-
return promise;
|
|
144
|
-
}
|
|
145
|
-
promise = mkdir(pathname, {
|
|
146
|
-
recursive: true
|
|
147
|
-
}).then(()=>{});
|
|
148
|
-
createDirectoryPromises.set(pathname, promise);
|
|
149
|
-
return promise;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Create temporary directory.
|
|
153
|
-
*/ async function createTemporaryDirectory() {
|
|
154
|
-
debug("Creating temporary directory");
|
|
155
|
-
const osTmpDirectory = tmpdir();
|
|
156
|
-
const path = join(osTmpDirectory, "argos." + randomBytes(16).toString("hex"));
|
|
157
|
-
await createDirectory(path);
|
|
158
|
-
debug(`Temporary directory created: ${path}`);
|
|
159
|
-
return path;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Check if the build name is dynamic.
|
|
163
|
-
*/ function checkIsDynamicBuildName(buildName) {
|
|
164
|
-
return Boolean(typeof buildName === "object" && buildName);
|
|
165
|
-
}
|
|
166
|
-
function createArgosReporterOptions(options) {
|
|
167
|
-
return options;
|
|
168
|
-
}
|
|
169
|
-
async function getParallelFromConfig(config) {
|
|
170
|
-
if (!config.shard) return null;
|
|
171
|
-
if (config.shard.total === 1) return null;
|
|
172
|
-
const argosConfig = await readConfig();
|
|
173
|
-
if (!argosConfig.parallelNonce) {
|
|
174
|
-
throw new Error("Playwright shard mode detected. Please specify ARGOS_PARALLEL_NONCE env variable. Read /parallel-testing");
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
total: config.shard.total,
|
|
178
|
-
nonce: argosConfig.parallelNonce,
|
|
179
|
-
index: config.shard.current
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Get the automatic screenshot name.
|
|
184
|
-
*/ function getAutomaticScreenshotName(test, result) {
|
|
185
|
-
let name = test.titlePath().join(" ");
|
|
186
|
-
name += result.retry > 0 ? ` #${result.retry + 1}` : "";
|
|
187
|
-
name += result.status === "failed" || result.status === "timedOut" ? " (failed)" : "";
|
|
188
|
-
return name;
|
|
189
|
-
}
|
|
190
|
-
class ArgosReporter {
|
|
191
|
-
rootUploadDirectoryPromise;
|
|
192
|
-
uploadDirectoryPromises;
|
|
193
|
-
config;
|
|
194
|
-
playwrightConfig;
|
|
195
|
-
uploadToArgos;
|
|
196
|
-
constructor(config){
|
|
197
|
-
this.config = config;
|
|
198
|
-
this.uploadToArgos = config.uploadToArgos ?? true;
|
|
199
|
-
this.rootUploadDirectoryPromise = null;
|
|
200
|
-
this.uploadDirectoryPromises = new Map();
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Write a file to the temporary directory.
|
|
204
|
-
*/ async writeFile(path, body) {
|
|
205
|
-
await createDirectory(dirname(path));
|
|
206
|
-
debug(`Writing file to ${path}`);
|
|
207
|
-
await writeFile(path, body);
|
|
208
|
-
debug(`File written to ${path}`);
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Copy a file to the temporary directory.
|
|
212
|
-
*/ async copyFile(from, to) {
|
|
213
|
-
await createDirectory(dirname(to));
|
|
214
|
-
debug(`Copying file from ${from} to ${to}`);
|
|
215
|
-
await copyFile(from, to);
|
|
216
|
-
debug(`File copied from ${from} to ${to}`);
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Copy the trace file if found in the result.
|
|
220
|
-
*/ async copyTraceIfFound(result, path) {
|
|
221
|
-
const trace = result.attachments.find(checkIsTrace) ?? null;
|
|
222
|
-
if (trace) {
|
|
223
|
-
await this.copyFile(trace.path, path + ".pw-trace.zip");
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Get the root upload directory (cached).
|
|
228
|
-
*/ getRootUploadDirectory() {
|
|
229
|
-
if (!this.rootUploadDirectoryPromise) {
|
|
230
|
-
this.rootUploadDirectoryPromise = createTemporaryDirectory();
|
|
231
|
-
}
|
|
232
|
-
return this.rootUploadDirectoryPromise;
|
|
233
|
-
}
|
|
234
|
-
onBegin(config, _suite) {
|
|
235
|
-
debug("ArgosReporter:onBegin");
|
|
236
|
-
this.playwrightConfig = config;
|
|
237
|
-
}
|
|
238
|
-
async onTestEnd(test, result) {
|
|
239
|
-
const buildName = checkIsDynamicBuildName(this.config.buildName) ? this.config.buildName.get(test) : this.config.buildName;
|
|
240
|
-
if (buildName === "") {
|
|
241
|
-
throw new Error('Argos "buildName" cannot be an empty string.');
|
|
242
|
-
}
|
|
243
|
-
const rootUploadDir = await this.getRootUploadDirectory();
|
|
244
|
-
const uploadDir = buildName ? join(rootUploadDir, buildName) : rootUploadDir;
|
|
245
|
-
debug("ArgosReporter:onTestEnd");
|
|
246
|
-
await Promise.all(result.attachments.map(async (attachment)=>{
|
|
247
|
-
if (checkIsArgosScreenshot(attachment) || checkIsArgosScreenshotMetadata(attachment)) {
|
|
248
|
-
const path = join(uploadDir, getAttachmentFilename(attachment.name));
|
|
249
|
-
await Promise.all([
|
|
250
|
-
this.copyFile(attachment.path, path),
|
|
251
|
-
this.copyTraceIfFound(result, path)
|
|
252
|
-
]);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
// Error screenshots are sent to Argos
|
|
256
|
-
if (checkIsAutomaticScreenshot(attachment)) {
|
|
257
|
-
const metadata = await getMetadataFromTestCase(test, result);
|
|
258
|
-
const name = getAutomaticScreenshotName(test, result);
|
|
259
|
-
const path = join(uploadDir, `${name}.png`);
|
|
260
|
-
await Promise.all([
|
|
261
|
-
this.writeFile(path + ".argos.json", JSON.stringify(metadata)),
|
|
262
|
-
this.copyFile(attachment.path, path),
|
|
263
|
-
this.copyTraceIfFound(result, path)
|
|
264
|
-
]);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
}));
|
|
268
|
-
}
|
|
269
|
-
async onEnd(result) {
|
|
270
|
-
debug("ArgosReporter:onEnd");
|
|
271
|
-
const rootUploadDir = await this.getRootUploadDirectory();
|
|
272
|
-
if (!this.uploadToArgos) {
|
|
273
|
-
debug("Not uploading to Argos because uploadToArgos is false.");
|
|
274
|
-
debug(`Upload directory: ${rootUploadDir}`);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
debug("Getting parallel from config");
|
|
278
|
-
const parallel = await getParallelFromConfig(this.playwrightConfig);
|
|
279
|
-
if (parallel) {
|
|
280
|
-
debug(`Using parallel config — total: ${parallel.total}, nonce: "${parallel.nonce}"`);
|
|
281
|
-
} else {
|
|
282
|
-
debug("Non-parallel mode");
|
|
283
|
-
}
|
|
284
|
-
const buildNameConfig = this.config.buildName;
|
|
285
|
-
const uploadOptions = {
|
|
286
|
-
files: [
|
|
287
|
-
"**/*.png"
|
|
288
|
-
],
|
|
289
|
-
parallel: parallel ?? undefined,
|
|
290
|
-
...this.config,
|
|
291
|
-
buildName: undefined,
|
|
292
|
-
metadata: {
|
|
293
|
-
testReport: {
|
|
294
|
-
status: result.status,
|
|
295
|
-
stats: {
|
|
296
|
-
startTime: result.startTime.toISOString(),
|
|
297
|
-
duration: result.duration
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
try {
|
|
303
|
-
if (checkIsDynamicBuildName(buildNameConfig)) {
|
|
304
|
-
debug(`Dynamic build names, uploading to Argos for each build name: ${buildNameConfig.values.join()}`);
|
|
305
|
-
const directories = await readdir(rootUploadDir);
|
|
306
|
-
// Check if the buildName.values are consistent with the directories created
|
|
307
|
-
if (directories.some((dir)=>!buildNameConfig.values.includes(dir))) {
|
|
308
|
-
throw new Error(`The \`buildName.values\` (${buildNameConfig.values.join(", ")}) are inconsistent with the \`buildName.get\` returns values (${directories.join(", ")}). Please fix the configuration.`);
|
|
309
|
-
}
|
|
310
|
-
// In non-parallel mode, we iterate over the directories to avoid creating useless builds
|
|
311
|
-
const iteratesOnBuildNames = parallel ? buildNameConfig.values : directories;
|
|
312
|
-
// Iterate over each build name and upload the screenshots
|
|
313
|
-
for (const buildName of iteratesOnBuildNames){
|
|
314
|
-
const uploadDir = join(rootUploadDir, buildName);
|
|
315
|
-
await createDirectory(uploadDir);
|
|
316
|
-
debug(`Uploading to Argos for build: ${buildName}`);
|
|
317
|
-
const res = await upload({
|
|
318
|
-
...uploadOptions,
|
|
319
|
-
root: uploadDir,
|
|
320
|
-
buildName
|
|
321
|
-
});
|
|
322
|
-
console.log(chalk.green(`✅ Argos "${buildName}" build created: ${res.build.url}`));
|
|
323
|
-
}
|
|
324
|
-
} else {
|
|
325
|
-
debug("Uploading to Argos");
|
|
326
|
-
const uploadDir = buildNameConfig ? join(rootUploadDir, buildNameConfig) : rootUploadDir;
|
|
327
|
-
const res = await upload({
|
|
328
|
-
...uploadOptions,
|
|
329
|
-
root: uploadDir,
|
|
330
|
-
buildName: buildNameConfig ?? undefined
|
|
331
|
-
});
|
|
332
|
-
console.log(chalk.green(`✅ Argos build created: ${res.build.url}`));
|
|
333
|
-
}
|
|
334
|
-
} catch (error) {
|
|
335
|
-
console.error(error);
|
|
336
|
-
return {
|
|
337
|
-
status: "failed"
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export { createArgosReporterOptions, ArgosReporter as default };
|