@azure/playwright 1.0.1-alpha.20251225.1 → 1.0.1-alpha.20251226.2
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/browser/common/messages.d.ts +46 -9
- package/dist/browser/common/messages.d.ts.map +1 -1
- package/dist/browser/common/messages.js +59 -22
- package/dist/browser/common/messages.js.map +1 -1
- package/dist/browser/common/types.d.ts +12 -0
- package/dist/browser/common/types.d.ts.map +1 -1
- package/dist/browser/common/types.js.map +1 -1
- package/dist/browser/reporter/playwrightReporter.d.ts +2 -1
- package/dist/browser/reporter/playwrightReporter.d.ts.map +1 -1
- package/dist/browser/reporter/playwrightReporter.js +63 -21
- package/dist/browser/reporter/playwrightReporter.js.map +1 -1
- package/dist/browser/utils/playwrightReporterStorageManager.d.ts +4 -5
- package/dist/browser/utils/playwrightReporterStorageManager.d.ts.map +1 -1
- package/dist/browser/utils/playwrightReporterStorageManager.js +149 -98
- package/dist/browser/utils/playwrightReporterStorageManager.js.map +1 -1
- package/dist/browser/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/browser/utils/playwrightServiceApicall.js +44 -30
- package/dist/browser/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/browser/utils/utils.d.ts +1 -0
- package/dist/browser/utils/utils.d.ts.map +1 -1
- package/dist/browser/utils/utils.js +19 -0
- package/dist/browser/utils/utils.js.map +1 -1
- package/dist/commonjs/common/messages.d.ts +46 -9
- package/dist/commonjs/common/messages.d.ts.map +1 -1
- package/dist/commonjs/common/messages.js +59 -22
- package/dist/commonjs/common/messages.js.map +1 -1
- package/dist/commonjs/common/types.d.ts +12 -0
- package/dist/commonjs/common/types.d.ts.map +1 -1
- package/dist/commonjs/common/types.js.map +1 -1
- package/dist/commonjs/reporter/playwrightReporter.d.ts +2 -1
- package/dist/commonjs/reporter/playwrightReporter.d.ts.map +1 -1
- package/dist/commonjs/reporter/playwrightReporter.js +62 -20
- package/dist/commonjs/reporter/playwrightReporter.js.map +1 -1
- package/dist/commonjs/utils/playwrightReporterStorageManager.d.ts +4 -5
- package/dist/commonjs/utils/playwrightReporterStorageManager.d.ts.map +1 -1
- package/dist/commonjs/utils/playwrightReporterStorageManager.js +147 -96
- package/dist/commonjs/utils/playwrightReporterStorageManager.js.map +1 -1
- package/dist/commonjs/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/commonjs/utils/playwrightServiceApicall.js +43 -29
- package/dist/commonjs/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/commonjs/utils/utils.d.ts +1 -0
- package/dist/commonjs/utils/utils.d.ts.map +1 -1
- package/dist/commonjs/utils/utils.js +21 -1
- package/dist/commonjs/utils/utils.js.map +1 -1
- package/dist/esm/common/messages.d.ts +46 -9
- package/dist/esm/common/messages.d.ts.map +1 -1
- package/dist/esm/common/messages.js +59 -22
- package/dist/esm/common/messages.js.map +1 -1
- package/dist/esm/common/types.d.ts +12 -0
- package/dist/esm/common/types.d.ts.map +1 -1
- package/dist/esm/common/types.js.map +1 -1
- package/dist/esm/reporter/playwrightReporter.d.ts +2 -1
- package/dist/esm/reporter/playwrightReporter.d.ts.map +1 -1
- package/dist/esm/reporter/playwrightReporter.js +63 -21
- package/dist/esm/reporter/playwrightReporter.js.map +1 -1
- package/dist/esm/utils/playwrightReporterStorageManager.d.ts +4 -5
- package/dist/esm/utils/playwrightReporterStorageManager.d.ts.map +1 -1
- package/dist/esm/utils/playwrightReporterStorageManager.js +149 -98
- package/dist/esm/utils/playwrightReporterStorageManager.js.map +1 -1
- package/dist/esm/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/esm/utils/playwrightServiceApicall.js +44 -30
- package/dist/esm/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/esm/utils/utils.d.ts +1 -0
- package/dist/esm/utils/utils.d.ts.map +1 -1
- package/dist/esm/utils/utils.js +19 -0
- package/dist/esm/utils/utils.js.map +1 -1
- package/dist/react-native/common/messages.d.ts +46 -9
- package/dist/react-native/common/messages.d.ts.map +1 -1
- package/dist/react-native/common/messages.js +59 -22
- package/dist/react-native/common/messages.js.map +1 -1
- package/dist/react-native/common/types.d.ts +12 -0
- package/dist/react-native/common/types.d.ts.map +1 -1
- package/dist/react-native/common/types.js.map +1 -1
- package/dist/react-native/reporter/playwrightReporter.d.ts +2 -1
- package/dist/react-native/reporter/playwrightReporter.d.ts.map +1 -1
- package/dist/react-native/reporter/playwrightReporter.js +63 -21
- package/dist/react-native/reporter/playwrightReporter.js.map +1 -1
- package/dist/react-native/utils/playwrightReporterStorageManager.d.ts +4 -5
- package/dist/react-native/utils/playwrightReporterStorageManager.d.ts.map +1 -1
- package/dist/react-native/utils/playwrightReporterStorageManager.js +149 -98
- package/dist/react-native/utils/playwrightReporterStorageManager.js.map +1 -1
- package/dist/react-native/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/react-native/utils/playwrightServiceApicall.js +44 -30
- package/dist/react-native/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/react-native/utils/utils.d.ts +1 -0
- package/dist/react-native/utils/utils.d.ts.map +1 -1
- package/dist/react-native/utils/utils.js +19 -0
- package/dist/react-native/utils/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PlaywrightReporterStorageManager } from "../utils/playwrightReporterStorageManager.js";
|
|
2
|
-
import { getHtmlReporterOutputFolder, getPortalTestRunUrl
|
|
2
|
+
import { getHtmlReporterOutputFolder, getPortalTestRunUrl } from "../utils/utils.js";
|
|
3
3
|
import { coreLogger } from "../common/logger.js";
|
|
4
4
|
import { PlaywrightServiceConfig } from "../common/playwrightServiceConfig.js";
|
|
5
5
|
import { ServiceAuth } from "../common/constants.js";
|
|
@@ -11,6 +11,7 @@ import { PlaywrightServiceApiCall } from "../utils/playwrightServiceApicall.js";
|
|
|
11
11
|
export default class PlaywrightReporter {
|
|
12
12
|
config;
|
|
13
13
|
workspaceMetadata = null;
|
|
14
|
+
isReportingEnabled = false;
|
|
14
15
|
/**
|
|
15
16
|
* Called when test run begins. Stores configuration for later use and validates serviceAuthType and
|
|
16
17
|
* retrieves workspace metadata.
|
|
@@ -18,34 +19,72 @@ export default class PlaywrightReporter {
|
|
|
18
19
|
*/
|
|
19
20
|
async onBegin(config) {
|
|
20
21
|
coreLogger.info(`Reporter configuration: ${JSON.stringify(config.reporter, null, 2)}`);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Validate that reporter is used only with ENTRA_ID authentication
|
|
22
|
+
this.config = config;
|
|
23
|
+
// Check authentication
|
|
24
24
|
const playwrightServiceConfig = PlaywrightServiceConfig.instance;
|
|
25
25
|
coreLogger.info(`Current authentication type: ${playwrightServiceConfig.serviceAuthType}`);
|
|
26
26
|
const isUsingAccessToken = playwrightServiceConfig.serviceAuthType === ServiceAuth.ACCESS_TOKEN;
|
|
27
27
|
if (isUsingAccessToken) {
|
|
28
|
-
|
|
28
|
+
console.error(ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH.message);
|
|
29
|
+
this.isReportingEnabled = false;
|
|
30
|
+
return;
|
|
29
31
|
}
|
|
30
32
|
coreLogger.info("Authentication validation passed - using ENTRA_ID");
|
|
31
|
-
this.config = config;
|
|
32
33
|
// Get workspace metadata for later use
|
|
33
34
|
try {
|
|
34
35
|
const playwrightServiceApiClient = new PlaywrightServiceApiCall();
|
|
35
36
|
this.workspaceMetadata = await playwrightServiceApiClient.getWorkspaceMetadata();
|
|
36
|
-
|
|
37
|
+
if (!this.workspaceMetadata?.storageUri) {
|
|
38
|
+
console.error(ServiceErrorMessageConstants.WORKSPACE_REPORTING_DISABLED.message);
|
|
39
|
+
console.error(ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);
|
|
40
|
+
this.isReportingEnabled = false;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.isReportingEnabled = true;
|
|
44
|
+
this.validateHtmlReporterConfiguration(config);
|
|
45
|
+
if (this.isReportingEnabled) {
|
|
46
|
+
console.log(ServiceErrorMessageConstants.REPORTING_ENABLED.message);
|
|
47
|
+
}
|
|
37
48
|
}
|
|
38
49
|
catch (error) {
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
51
|
+
console.error(`${ServiceErrorMessageConstants.WORKSPACE_METADATA_FETCH_FAILED.message}Error: ${errorMessage} `);
|
|
52
|
+
this.isReportingEnabled = false;
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
/**
|
|
44
|
-
* Called when test run ends. Uploads Playwright test report to Azure Storage.
|
|
56
|
+
* Called when test run ends. Uploads Playwright test report to Azure Storage if reporting is enabled.
|
|
45
57
|
*/
|
|
46
58
|
async onEnd() {
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
if (this.isReportingEnabled) {
|
|
60
|
+
console.log(ServiceErrorMessageConstants.COLLECTING_ARTIFACTS.message);
|
|
61
|
+
const uploadResult = await this.uploadHtmlReport();
|
|
62
|
+
if (uploadResult.success) {
|
|
63
|
+
if (uploadResult.partialSuccess &&
|
|
64
|
+
uploadResult.failedFileDetails &&
|
|
65
|
+
uploadResult.failedFileDetails.length > 0) {
|
|
66
|
+
console.log("Warning: Failed to upload the following files:");
|
|
67
|
+
uploadResult.failedFileDetails.forEach((fileDetail) => {
|
|
68
|
+
console.log(` - ${fileDetail.fileName}, ERROR: ${fileDetail.error}`);
|
|
69
|
+
});
|
|
70
|
+
console.log(ServiceErrorMessageConstants.REPORTING_STATUS_PARTIAL.message);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(ServiceErrorMessageConstants.REPORTING_STATUS_SUCCESS.message);
|
|
74
|
+
}
|
|
75
|
+
// Display portal URL for both full and partial success
|
|
76
|
+
if (this.workspaceMetadata) {
|
|
77
|
+
const portalUrl = getPortalTestRunUrl(this.workspaceMetadata);
|
|
78
|
+
console.log(ServiceErrorMessageConstants.TEST_REPORT_VIEW_URL.formatWithUrl(portalUrl));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.error(ServiceErrorMessageConstants.REPORTING_STATUS_FAILED.message);
|
|
83
|
+
if (uploadResult.errorMessage) {
|
|
84
|
+
console.error(`Error: ${uploadResult.errorMessage}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
49
88
|
}
|
|
50
89
|
async uploadHtmlReport() {
|
|
51
90
|
try {
|
|
@@ -53,22 +92,23 @@ export default class PlaywrightReporter {
|
|
|
53
92
|
coreLogger.info(`Resolved Playwright test report output folder: ${outputFolder}`);
|
|
54
93
|
const storageManager = new PlaywrightReporterStorageManager();
|
|
55
94
|
coreLogger.info("Starting Playwright test report upload process");
|
|
56
|
-
await storageManager.uploadPlaywrightHtmlReportAfterTests(outputFolder, this.workspaceMetadata);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (this.workspaceMetadata) {
|
|
60
|
-
const portalUrl = getPortalTestRunUrl(this.workspaceMetadata);
|
|
61
|
-
process.stdout.write(`You can view test report at: ${portalUrl}\n`);
|
|
95
|
+
const uploadResult = await storageManager.uploadPlaywrightHtmlReportAfterTests(outputFolder, this.workspaceMetadata);
|
|
96
|
+
if (uploadResult.success) {
|
|
97
|
+
coreLogger.info(`Playwright Test report uploaded successfully to Azure Storage.`);
|
|
62
98
|
}
|
|
99
|
+
return uploadResult;
|
|
63
100
|
}
|
|
64
101
|
catch (error) {
|
|
65
102
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
66
|
-
|
|
103
|
+
coreLogger.error(`Upload failed: ${errorMessage}`);
|
|
104
|
+
return { success: false, errorMessage };
|
|
67
105
|
}
|
|
68
106
|
}
|
|
69
107
|
validateHtmlReporterConfiguration(config) {
|
|
70
108
|
if (!config.reporter || !Array.isArray(config.reporter)) {
|
|
71
|
-
|
|
109
|
+
console.error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
|
|
110
|
+
this.isReportingEnabled = false;
|
|
111
|
+
return;
|
|
72
112
|
}
|
|
73
113
|
const hasHtmlReporter = config.reporter.some((reporter) => {
|
|
74
114
|
if (typeof reporter === "string") {
|
|
@@ -80,7 +120,9 @@ export default class PlaywrightReporter {
|
|
|
80
120
|
return false;
|
|
81
121
|
});
|
|
82
122
|
if (!hasHtmlReporter) {
|
|
83
|
-
|
|
123
|
+
console.error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
|
|
124
|
+
this.isReportingEnabled = false;
|
|
125
|
+
return;
|
|
84
126
|
}
|
|
85
127
|
coreLogger.info("HTML reporter validation passed - HTML reporter is configured");
|
|
86
128
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwrightReporter.js","sourceRoot":"","sources":["../../../src/reporter/playwrightReporter.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\nimport type { FullConfig, Reporter } from \"@playwright/test/reporter\";\nimport { PlaywrightReporterStorageManager } from \"../utils/playwrightReporterStorageManager.js\";\nimport {\n getHtmlReporterOutputFolder,\n getPortalTestRunUrl,\n exitWithFailureMessage,\n} from \"../utils/utils.js\";\nimport { coreLogger } from \"../common/logger.js\";\nimport { PlaywrightServiceConfig } from \"../common/playwrightServiceConfig.js\";\nimport { ServiceAuth } from \"../common/constants.js\";\nimport { ServiceErrorMessageConstants } from \"../common/messages.js\";\nimport { PlaywrightServiceApiCall } from \"../utils/playwrightServiceApicall.js\";\nimport type { WorkspaceMetaData } from \"../common/types.js\";\n\n/**\n * Azure Playwright Reporter - Uploads generated Playwright test report folder to Azure Storage.\n */\n\nexport default class PlaywrightReporter implements Reporter {\n private config: FullConfig | undefined;\n private workspaceMetadata: WorkspaceMetaData | null = null;\n\n /**\n * Called when test run begins. Stores configuration for later use and validates serviceAuthType and\n * retrieves workspace metadata.\n * @param config - Playwright test configuration\n */\n\n async onBegin(config: FullConfig): Promise<void> {\n coreLogger.info(`Reporter configuration: ${JSON.stringify(config.reporter, null, 2)}`);\n\n // Validate that HTML reporter is configured\n this.validateHtmlReporterConfiguration(config);\n\n // Validate that reporter is used only with ENTRA_ID authentication\n const playwrightServiceConfig = PlaywrightServiceConfig.instance;\n coreLogger.info(`Current authentication type: ${playwrightServiceConfig.serviceAuthType}`);\n const isUsingAccessToken = playwrightServiceConfig.serviceAuthType === ServiceAuth.ACCESS_TOKEN;\n if (isUsingAccessToken) {\n exitWithFailureMessage(ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH);\n }\n coreLogger.info(\"Authentication validation passed - using ENTRA_ID\");\n this.config = config;\n // Get workspace metadata for later use\n try {\n const playwrightServiceApiClient = new PlaywrightServiceApiCall();\n this.workspaceMetadata = await playwrightServiceApiClient.getWorkspaceMetadata();\n process.stdout.write(\"Initializing reporting for this test run.\\n\");\n } catch (error) {\n coreLogger.error(\n `Failed to get workspace metadata: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.stderr.write(\"Failed to initialize reporting for this test run.\\n\");\n }\n }\n\n /**\n * Called when test run ends. Uploads Playwright test report to Azure Storage.\n */\n\n async onEnd(): Promise<void> {\n process.stdout.write(`Uploading Playwright Test report in Azure storage account.\\n`);\n await this.uploadHtmlReport();\n }\n\n private async uploadHtmlReport(): Promise<void> {\n try {\n const outputFolder = getHtmlReporterOutputFolder(this.config);\n coreLogger.info(`Resolved Playwright test report output folder: ${outputFolder}`);\n const storageManager = new PlaywrightReporterStorageManager();\n coreLogger.info(\"Starting Playwright test report upload process\");\n await storageManager.uploadPlaywrightHtmlReportAfterTests(\n outputFolder,\n this.workspaceMetadata,\n );\n coreLogger.info(`✅ Playwright Test report uploaded successfully to Azure Storage.`);\n\n // Display portal URL for test report\n if (this.workspaceMetadata) {\n const portalUrl = getPortalTestRunUrl(this.workspaceMetadata);\n process.stdout.write(`You can view test report at: ${portalUrl}\\n`);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n process.stderr.write(`❌ Failed to upload Playwright test report: ${errorMessage}\\n`);\n }\n }\n\n private validateHtmlReporterConfiguration(config: FullConfig): void {\n if (!config.reporter || !Array.isArray(config.reporter)) {\n throw new Error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);\n }\n\n const hasHtmlReporter = config.reporter.some((reporter) => {\n if (typeof reporter === \"string\") {\n return reporter === \"html\";\n }\n if (Array.isArray(reporter) && reporter.length > 0) {\n return reporter[0] === \"html\";\n }\n return false;\n });\n\n if (!hasHtmlReporter) {\n exitWithFailureMessage(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED);\n }\n\n coreLogger.info(\"HTML reporter validation passed - HTML reporter is configured\");\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"playwrightReporter.js","sourceRoot":"","sources":["../../../src/reporter/playwrightReporter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gCAAgC,EAAE,MAAM,8CAA8C,CAAC;AAChG,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAGhF;;GAEG;AAEH,MAAM,CAAC,OAAO,OAAO,kBAAkB;IAC7B,MAAM,CAAyB;IAC/B,iBAAiB,GAA6B,IAAI,CAAC;IACnD,kBAAkB,GAAG,KAAK,CAAC;IAEnC;;;;OAIG;IAEH,KAAK,CAAC,OAAO,CAAC,MAAkB;QAC9B,UAAU,CAAC,IAAI,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAEvF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,uBAAuB;QACvB,MAAM,uBAAuB,GAAG,uBAAuB,CAAC,QAAQ,CAAC;QACjE,UAAU,CAAC,IAAI,CAAC,gCAAgC,uBAAuB,CAAC,eAAe,EAAE,CAAC,CAAC;QAC3F,MAAM,kBAAkB,GAAG,uBAAuB,CAAC,eAAe,KAAK,WAAW,CAAC,YAAY,CAAC;QAChG,IAAI,kBAAkB,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC;YACjF,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;YAChC,OAAO;QACT,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;QAErE,uCAAuC;QACvC,IAAI,CAAC;YACH,MAAM,0BAA0B,GAAG,IAAI,wBAAwB,EAAE,CAAC;YAClE,IAAI,CAAC,iBAAiB,GAAG,MAAM,0BAA0B,CAAC,oBAAoB,EAAE,CAAC;YAEjF,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,UAAU,EAAE,CAAC;gBACxC,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC;gBACjF,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;gBAC1E,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;gBAChC,OAAO;YACT,CAAC;YAED,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAE/B,IAAI,CAAC,iCAAiC,CAAC,MAAM,CAAC,CAAC;YAC/C,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC5B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,OAAO,CAAC,KAAK,CACX,GAAG,4BAA4B,CAAC,+BAA+B,CAAC,OAAO,UAAU,YAAY,GAAG,CACjG,CAAC;YACF,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;OAEG;IAEH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YACvE,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAEnD,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACzB,IACE,YAAY,CAAC,cAAc;oBAC3B,YAAY,CAAC,iBAAiB;oBAC9B,YAAY,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EACzC,CAAC;oBACD,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;oBAC9D,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE;wBACpD,OAAO,CAAC,GAAG,CAAC,OAAO,UAAU,CAAC,QAAQ,YAAY,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;oBACxE,CAAC,CAAC,CAAC;oBACH,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;gBAC7E,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;gBAC7E,CAAC;gBACD,uDAAuD;gBACvD,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC3B,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;oBAC9D,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,oBAAoB,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC1F,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;gBAC5E,IAAI,YAAY,CAAC,YAAY,EAAE,CAAC;oBAC9B,OAAO,CAAC,KAAK,CAAC,UAAU,YAAY,CAAC,YAAY,EAAE,CAAC,CAAC;gBACvD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9D,UAAU,CAAC,IAAI,CAAC,kDAAkD,YAAY,EAAE,CAAC,CAAC;YAClF,MAAM,cAAc,GAAG,IAAI,gCAAgC,EAAE,CAAC;YAC9D,UAAU,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAClE,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,oCAAoC,CAC5E,YAAY,EACZ,IAAI,CAAC,iBAAiB,CACvB,CAAC;YAEF,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;YACpF,CAAC;YACD,OAAO,YAAY,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YAC9E,UAAU,CAAC,KAAK,CAAC,kBAAkB,YAAY,EAAE,CAAC,CAAC;YACnD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QAC1C,CAAC;IACH,CAAC;IAEO,iCAAiC,CAAC,MAAkB;QAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxD,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YAC3E,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACjC,OAAO,QAAQ,KAAK,MAAM,CAAC;YAC7B,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnD,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;YAChC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YAC3E,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;YAChC,OAAO;QACT,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\nimport type { FullConfig, Reporter } from \"@playwright/test/reporter\";\nimport { PlaywrightReporterStorageManager } from \"../utils/playwrightReporterStorageManager.js\";\nimport { getHtmlReporterOutputFolder, getPortalTestRunUrl } from \"../utils/utils.js\";\nimport { coreLogger } from \"../common/logger.js\";\nimport { PlaywrightServiceConfig } from \"../common/playwrightServiceConfig.js\";\nimport { ServiceAuth } from \"../common/constants.js\";\nimport { ServiceErrorMessageConstants } from \"../common/messages.js\";\nimport { PlaywrightServiceApiCall } from \"../utils/playwrightServiceApicall.js\";\nimport type { WorkspaceMetaData, UploadResult } from \"../common/types.js\";\n\n/**\n * Azure Playwright Reporter - Uploads generated Playwright test report folder to Azure Storage.\n */\n\nexport default class PlaywrightReporter implements Reporter {\n private config: FullConfig | undefined;\n private workspaceMetadata: WorkspaceMetaData | null = null;\n private isReportingEnabled = false;\n\n /**\n * Called when test run begins. Stores configuration for later use and validates serviceAuthType and\n * retrieves workspace metadata.\n * @param config - Playwright test configuration\n */\n\n async onBegin(config: FullConfig): Promise<void> {\n coreLogger.info(`Reporter configuration: ${JSON.stringify(config.reporter, null, 2)}`);\n\n this.config = config;\n\n // Check authentication\n const playwrightServiceConfig = PlaywrightServiceConfig.instance;\n coreLogger.info(`Current authentication type: ${playwrightServiceConfig.serviceAuthType}`);\n const isUsingAccessToken = playwrightServiceConfig.serviceAuthType === ServiceAuth.ACCESS_TOKEN;\n if (isUsingAccessToken) {\n console.error(ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH.message);\n this.isReportingEnabled = false;\n return;\n }\n coreLogger.info(\"Authentication validation passed - using ENTRA_ID\");\n\n // Get workspace metadata for later use\n try {\n const playwrightServiceApiClient = new PlaywrightServiceApiCall();\n this.workspaceMetadata = await playwrightServiceApiClient.getWorkspaceMetadata();\n\n if (!this.workspaceMetadata?.storageUri) {\n console.error(ServiceErrorMessageConstants.WORKSPACE_REPORTING_DISABLED.message);\n console.error(ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);\n this.isReportingEnabled = false;\n return;\n }\n\n this.isReportingEnabled = true;\n\n this.validateHtmlReporterConfiguration(config);\n if (this.isReportingEnabled) {\n console.log(ServiceErrorMessageConstants.REPORTING_ENABLED.message);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(\n `${ServiceErrorMessageConstants.WORKSPACE_METADATA_FETCH_FAILED.message}Error: ${errorMessage} `,\n );\n this.isReportingEnabled = false;\n }\n }\n\n /**\n * Called when test run ends. Uploads Playwright test report to Azure Storage if reporting is enabled.\n */\n\n async onEnd(): Promise<void> {\n if (this.isReportingEnabled) {\n console.log(ServiceErrorMessageConstants.COLLECTING_ARTIFACTS.message);\n const uploadResult = await this.uploadHtmlReport();\n\n if (uploadResult.success) {\n if (\n uploadResult.partialSuccess &&\n uploadResult.failedFileDetails &&\n uploadResult.failedFileDetails.length > 0\n ) {\n console.log(\"Warning: Failed to upload the following files:\");\n uploadResult.failedFileDetails.forEach((fileDetail) => {\n console.log(` - ${fileDetail.fileName}, ERROR: ${fileDetail.error}`);\n });\n console.log(ServiceErrorMessageConstants.REPORTING_STATUS_PARTIAL.message);\n } else {\n console.log(ServiceErrorMessageConstants.REPORTING_STATUS_SUCCESS.message);\n }\n // Display portal URL for both full and partial success\n if (this.workspaceMetadata) {\n const portalUrl = getPortalTestRunUrl(this.workspaceMetadata);\n console.log(ServiceErrorMessageConstants.TEST_REPORT_VIEW_URL.formatWithUrl(portalUrl));\n }\n } else {\n console.error(ServiceErrorMessageConstants.REPORTING_STATUS_FAILED.message);\n if (uploadResult.errorMessage) {\n console.error(`Error: ${uploadResult.errorMessage}`);\n }\n }\n }\n }\n\n private async uploadHtmlReport(): Promise<UploadResult> {\n try {\n const outputFolder = getHtmlReporterOutputFolder(this.config);\n coreLogger.info(`Resolved Playwright test report output folder: ${outputFolder}`);\n const storageManager = new PlaywrightReporterStorageManager();\n coreLogger.info(\"Starting Playwright test report upload process\");\n const uploadResult = await storageManager.uploadPlaywrightHtmlReportAfterTests(\n outputFolder,\n this.workspaceMetadata,\n );\n\n if (uploadResult.success) {\n coreLogger.info(`Playwright Test report uploaded successfully to Azure Storage.`);\n }\n return uploadResult;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n coreLogger.error(`Upload failed: ${errorMessage}`);\n return { success: false, errorMessage };\n }\n }\n\n private validateHtmlReporterConfiguration(config: FullConfig): void {\n if (!config.reporter || !Array.isArray(config.reporter)) {\n console.error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);\n this.isReportingEnabled = false;\n return;\n }\n\n const hasHtmlReporter = config.reporter.some((reporter) => {\n if (typeof reporter === \"string\") {\n return reporter === \"html\";\n }\n if (Array.isArray(reporter) && reporter.length > 0) {\n return reporter[0] === \"html\";\n }\n return false;\n });\n\n if (!hasHtmlReporter) {\n console.error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);\n this.isReportingEnabled = false;\n return;\n }\n\n coreLogger.info(\"HTML reporter validation passed - HTML reporter is configured\");\n }\n}\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { TokenCredential } from "@azure/core-auth";
|
|
2
|
-
import type { WorkspaceMetaData } from "../common/types.js";
|
|
2
|
+
import type { WorkspaceMetaData, UploadResult } from "../common/types.js";
|
|
3
3
|
export declare class PlaywrightReporterStorageManager {
|
|
4
|
-
uploadHtmlReportFolder(credential: TokenCredential, runId: string, outputFolder: string, workspaceDetails: WorkspaceMetaData): Promise<
|
|
5
|
-
private
|
|
6
|
-
|
|
7
|
-
uploadPlaywrightHtmlReportAfterTests(outputFolderName?: string, workspaceMetadata?: WorkspaceMetaData | null): Promise<void>;
|
|
4
|
+
uploadHtmlReportFolder(credential: TokenCredential, runId: string, outputFolder: string, workspaceDetails: WorkspaceMetaData): Promise<UploadResult>;
|
|
5
|
+
private modifyTraceIndexHtml;
|
|
6
|
+
uploadPlaywrightHtmlReportAfterTests(outputFolderName?: string, workspaceMetadata?: WorkspaceMetaData | null): Promise<UploadResult>;
|
|
8
7
|
private uploadFolderInParallel;
|
|
9
8
|
private uploadWithConcurrencyControl;
|
|
10
9
|
private executeWithOptimizedBatching;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwrightReporterStorageManager.d.ts","sourceRoot":"","sources":["../../../src/utils/playwrightReporterStorageManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"playwrightReporterStorageManager.d.ts","sourceRoot":"","sources":["../../../src/utils/playwrightReporterStorageManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAaxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE1E,qBAAa,gCAAgC;IAErC,sBAAsB,CAC1B,UAAU,EAAE,eAAe,EAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,iBAAiB,GAClC,OAAO,CAAC,YAAY,CAAC;YA8FV,oBAAoB;IAmD5B,oCAAoC,CACxC,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAC3C,OAAO,CAAC,YAAY,CAAC;YAiDV,sBAAsB;YA4GtB,4BAA4B;YAwB5B,4BAA4B;YA4E5B,yBAAyB;YA8CzB,sBAAsB;CAsCrC"}
|
|
@@ -2,148 +2,166 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { BlobServiceClient } from "@azure/storage-blob";
|
|
4
4
|
import { coreLogger } from "../common/logger.js";
|
|
5
|
-
import { readFileSync,
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, createReadStream } from "fs";
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { UploadConstants } from "../common/constants.js";
|
|
8
8
|
import { ServiceErrorMessageConstants } from "../common/messages.js";
|
|
9
|
-
import { populateValuesFromServiceUrl, calculateOptimalConcurrency, collectAllFiles, } from "./utils.js";
|
|
9
|
+
import { populateValuesFromServiceUrl, calculateOptimalConcurrency, collectAllFiles, getStorageAccountNameFromUri, } from "./utils.js";
|
|
10
10
|
import { PlaywrightServiceConfig } from "../common/playwrightServiceConfig.js";
|
|
11
11
|
export class PlaywrightReporterStorageManager {
|
|
12
|
+
// Uploads the HTML report folder to Azure Storage
|
|
12
13
|
async uploadHtmlReportFolder(credential, runId, outputFolder, workspaceDetails) {
|
|
13
14
|
coreLogger.info(`Starting HTML report upload for runId: ${runId}, outputFolder: ${outputFolder}`);
|
|
14
15
|
try {
|
|
15
16
|
coreLogger.info(`Received workspace details: ${JSON.stringify(workspaceDetails, null, 2)}`);
|
|
16
17
|
if (!workspaceDetails.storageUri) {
|
|
17
|
-
|
|
18
|
+
coreLogger.error("Storage URI not found in workspace details");
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
errorMessage: ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message,
|
|
22
|
+
};
|
|
18
23
|
}
|
|
19
|
-
coreLogger.info(`Extracting storage account from URI: ${workspaceDetails.storageUri}`);
|
|
20
24
|
const blobServiceClient = new BlobServiceClient(workspaceDetails?.storageUri, credential);
|
|
21
25
|
const serviceUrlInfo = populateValuesFromServiceUrl();
|
|
22
26
|
if (!serviceUrlInfo?.accountId) {
|
|
23
|
-
|
|
27
|
+
coreLogger.error("Unable to extract workspace ID from service URL");
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
errorMessage: ServiceErrorMessageConstants.UNABLE_TO_EXTRACT_WORKSPACE_ID.message,
|
|
31
|
+
};
|
|
24
32
|
}
|
|
25
33
|
const containerName = serviceUrlInfo.accountId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
26
34
|
const containerClient = blobServiceClient.getContainerClient(containerName);
|
|
27
35
|
const containerExists = await containerClient.exists();
|
|
28
36
|
if (!containerExists) {
|
|
29
37
|
await containerClient.create();
|
|
30
|
-
console.log(`Created new container for this workspace: ${containerName}`);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
console.log(`Using existing container for this workspace: ${containerName}`);
|
|
34
38
|
}
|
|
35
39
|
const folderName = runId;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
await this.
|
|
39
|
-
await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
|
|
40
|
-
|
|
40
|
+
const storageAccountName = getStorageAccountNameFromUri(workspaceDetails?.storageUri || "") || "unknown";
|
|
41
|
+
console.log(ServiceErrorMessageConstants.UPLOADING_ARTIFACTS.formatWithDetails(storageAccountName, containerName, folderName));
|
|
42
|
+
await this.modifyTraceIndexHtml(outputFolder);
|
|
43
|
+
const uploadResults = await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
|
|
44
|
+
if (uploadResults.totalFiles === 0) {
|
|
45
|
+
return { success: false, errorMessage: "No files found to upload" };
|
|
46
|
+
}
|
|
47
|
+
const failedFiles = uploadResults.totalFiles - uploadResults.uploadedFiles.length;
|
|
48
|
+
if (failedFiles > 0) {
|
|
49
|
+
// Get list of failed file names by comparing total files with uploaded files
|
|
50
|
+
const uploadedSet = new Set(uploadResults.uploadedFiles);
|
|
51
|
+
const allFiles = collectAllFiles(outputFolder, outputFolder, folderName);
|
|
52
|
+
const failedFileNames = allFiles
|
|
53
|
+
.filter((file) => !uploadedSet.has(file.relativePath))
|
|
54
|
+
.map((file) => file.relativePath);
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
partialSuccess: true,
|
|
58
|
+
failedFileCount: failedFiles,
|
|
59
|
+
totalFiles: uploadResults.totalFiles,
|
|
60
|
+
failedFiles: failedFileNames,
|
|
61
|
+
failedFileDetails: uploadResults.failedFileDetails,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { success: true };
|
|
41
65
|
}
|
|
42
66
|
catch (error) {
|
|
43
67
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
44
|
-
coreLogger.error(`Failed to upload HTML report: ${
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
coreLogger.info(`Resolving sasworker.js path: ${sasWorkerPath}`);
|
|
53
|
-
if (!existsSync(sasWorkerPath)) {
|
|
54
|
-
coreLogger.error(`sasworker.js file not found at path: ${sasWorkerPath}`);
|
|
55
|
-
return;
|
|
68
|
+
coreLogger.error(`Failed to upload HTML report: ${error}`);
|
|
69
|
+
if (error instanceof Error &&
|
|
70
|
+
(error.message.includes("AuthorizationFailure") ||
|
|
71
|
+
error.message.includes("not authorized to perform this operation"))) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
errorMessage: ServiceErrorMessageConstants.STORAGE_AUTHORIZATION_FAILED.message,
|
|
75
|
+
};
|
|
56
76
|
}
|
|
57
|
-
|
|
58
|
-
const fileContent = readFileSync(sasWorkerPath);
|
|
59
|
-
const blobName = `${folderName}/sasworker.js`;
|
|
60
|
-
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
|
|
61
|
-
await blockBlobClient.upload(fileContent, fileContent.length, {
|
|
62
|
-
blobHTTPHeaders: {
|
|
63
|
-
blobContentType: "application/javascript",
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
coreLogger.info("✅ Uploaded service worker file as sasworker.js");
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
coreLogger.error(`Error uploading service worker file: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
-
throw error;
|
|
77
|
+
return { success: false, errorMessage };
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
|
-
async
|
|
74
|
-
coreLogger.info(`Starting
|
|
75
|
-
const indexPath = join(outputFolder, "index.html");
|
|
80
|
+
async modifyTraceIndexHtml(outputFolder) {
|
|
81
|
+
coreLogger.info(`Starting trace modification for folder: ${outputFolder}`);
|
|
82
|
+
const indexPath = join(outputFolder, "trace/index.html");
|
|
76
83
|
if (!existsSync(indexPath)) {
|
|
77
|
-
coreLogger.error(`index.html not found at path: ${indexPath}`);
|
|
84
|
+
coreLogger.error(`trace/index.html not found at path: ${indexPath}`);
|
|
78
85
|
return;
|
|
79
86
|
}
|
|
80
|
-
coreLogger.info(`Found index.html at: ${indexPath}`);
|
|
87
|
+
coreLogger.info(`Found trace/index.html at: ${indexPath}`);
|
|
81
88
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
const redirectTraceviewerScript = `<!DOCTYPE html>
|
|
90
|
+
<html>
|
|
91
|
+
<head>
|
|
92
|
+
<meta charset="utf-8">
|
|
93
|
+
<title>Redirecting to Trace Viewer...</title>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
85
96
|
<script>
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
const url = new URL(location.href);
|
|
98
|
+
const traceParam = url.searchParams.get('trace');
|
|
99
|
+
const trace = new URL(traceParam);
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (!navigator.serviceWorker.controller && !sessionStorage.getItem('__sw_bootstrap__')) {
|
|
95
|
-
sessionStorage.setItem('__sw_bootstrap__', '1');
|
|
96
|
-
location.reload();
|
|
101
|
+
// Copy all query parameters from the current URL to the trace URL
|
|
102
|
+
// This includes SAS tokens (sv, sr, sig, etc.) that are on the main URL
|
|
103
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
104
|
+
if (key !== 'trace') {
|
|
105
|
+
trace.searchParams.set(key, value);
|
|
97
106
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
coreLogger.info("Successfully wrote modified HTML content to file");
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
coreLogger.error("Title tag not found in index.html - unable to inject service worker script");
|
|
113
|
-
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const publicTraceViewer = new URL('https://trace.playwright.dev/');
|
|
110
|
+
publicTraceViewer.searchParams.set('trace', trace.toString());
|
|
111
|
+
location.href = publicTraceViewer.toString();
|
|
112
|
+
</script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
115
|
+
`;
|
|
116
|
+
writeFileSync(indexPath, redirectTraceviewerScript, "utf-8");
|
|
117
|
+
coreLogger.info("Successfully updated TraceViewer index file");
|
|
114
118
|
}
|
|
115
119
|
catch (error) {
|
|
116
|
-
coreLogger.error(`Error modifying index.html: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
-
|
|
120
|
+
coreLogger.error(`Error modifying trace/index.html: ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
+
return;
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
// Uploads the entire Playwright HTML report folder after tests complete.
|
|
121
125
|
async uploadPlaywrightHtmlReportAfterTests(outputFolderName, workspaceMetadata) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
try {
|
|
127
|
+
coreLogger.info(`Starting post-test HTML report upload, folder name: ${outputFolderName || "default (playwright-report)"}`);
|
|
128
|
+
const cred = PlaywrightServiceConfig.instance.credential;
|
|
129
|
+
if (!cred) {
|
|
130
|
+
coreLogger.error("No credential found for authentication");
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
errorMessage: ServiceErrorMessageConstants.NO_CRED_ENTRA_AUTH_ERROR.message,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
coreLogger.info("Credential found for authentication");
|
|
137
|
+
const folderName = outputFolderName || "playwright-report";
|
|
138
|
+
const outputFolderPath = join(process.cwd(), folderName);
|
|
139
|
+
if (!existsSync(outputFolderPath)) {
|
|
140
|
+
coreLogger.error(`HTML report folder not found: ${outputFolderPath}`);
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
errorMessage: ServiceErrorMessageConstants.PLAYWRIGHT_TEST_REPORT_NOT_FOUND.formatWithFolder(folderName),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
coreLogger.info(`HTML report folder found: ${outputFolderPath}`);
|
|
147
|
+
const testRunId = PlaywrightServiceConfig.instance.runId;
|
|
148
|
+
coreLogger.info(`Starting upload for test run ID: ${testRunId}`);
|
|
149
|
+
const result = await this.uploadHtmlReportFolder(cred, testRunId, outputFolderPath, workspaceMetadata);
|
|
150
|
+
coreLogger.info(`Completed upload for test run ID: ${testRunId}`);
|
|
151
|
+
return result;
|
|
127
152
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
coreLogger.error(`HTML report folder not found: ${outputFolderPath}`);
|
|
133
|
-
throw new Error(ServiceErrorMessageConstants.HTML_REPORT_FOLDER_NOT_FOUND.formatWithFolder(folderName));
|
|
153
|
+
catch (error) {
|
|
154
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
155
|
+
coreLogger.error(`Upload failed: ${errorMessage}`);
|
|
156
|
+
return { success: false, errorMessage };
|
|
134
157
|
}
|
|
135
|
-
coreLogger.info(`HTML report folder found: ${outputFolderPath}`);
|
|
136
|
-
const testRunId = PlaywrightServiceConfig.instance.runId;
|
|
137
|
-
coreLogger.info(`Starting upload for test run ID: ${testRunId}`);
|
|
138
|
-
await this.uploadHtmlReportFolder(cred, testRunId, outputFolderPath, workspaceMetadata);
|
|
139
|
-
coreLogger.info(`Completed upload for test run ID: ${testRunId}`);
|
|
140
158
|
}
|
|
141
159
|
// Parallel Upload Engine - Core upload orchestration with performance optimization
|
|
142
160
|
async uploadFolderInParallel(containerClient, folderPath, basePath, runIdFolderPrefix) {
|
|
143
161
|
// Sort by size descending - upload large files first for better parallelization
|
|
144
162
|
const filesToUpload = collectAllFiles(folderPath, basePath, runIdFolderPrefix).sort((a, b) => b.size - a.size);
|
|
145
163
|
if (filesToUpload.length === 0) {
|
|
146
|
-
return { uploadedFiles: [], totalFiles: 0, totalSize: 0, uploadTime: 0 };
|
|
164
|
+
return { uploadedFiles: [], failedFiles: [], totalFiles: 0, totalSize: 0, uploadTime: 0 };
|
|
147
165
|
}
|
|
148
166
|
const totalSize = filesToUpload.reduce((sum, file) => sum + file.size, 0);
|
|
149
167
|
const concurrency = calculateOptimalConcurrency(filesToUpload);
|
|
@@ -165,13 +183,41 @@ export class PlaywrightReporterStorageManager {
|
|
|
165
183
|
errors.forEach((error, index) => {
|
|
166
184
|
coreLogger.error(` ${index + 1}. ${error}`);
|
|
167
185
|
});
|
|
168
|
-
|
|
186
|
+
// Get failed file names and their error messages
|
|
187
|
+
const uploadedSet = new Set(results
|
|
188
|
+
.filter((r) => r.status === "fulfilled")
|
|
189
|
+
.map((r) => r.value));
|
|
190
|
+
const failedFileNames = filesToUpload
|
|
191
|
+
.filter((file) => !uploadedSet.has(file.relativePath))
|
|
192
|
+
.map((file) => file.relativePath);
|
|
193
|
+
// Create detailed error mapping
|
|
194
|
+
const failedFileDetails = [];
|
|
195
|
+
results.forEach((result, index) => {
|
|
196
|
+
if (result.status === "rejected") {
|
|
197
|
+
const fileName = filesToUpload[index]?.relativePath || `File at index ${index}`;
|
|
198
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
199
|
+
failedFileDetails.push({ fileName, error: errorMessage });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// Log error but don't throw to prevent breaking HTML reporter
|
|
203
|
+
coreLogger.error(`Upload failed: ${failed} files could not be uploaded. Sample errors: ${errors.join(", ")}`);
|
|
204
|
+
return {
|
|
205
|
+
uploadedFiles: results
|
|
206
|
+
.filter((r) => r.status === "fulfilled")
|
|
207
|
+
.map((r) => r.value),
|
|
208
|
+
failedFiles: failedFileNames,
|
|
209
|
+
failedFileDetails,
|
|
210
|
+
totalFiles: filesToUpload.length,
|
|
211
|
+
totalSize,
|
|
212
|
+
uploadTime,
|
|
213
|
+
};
|
|
169
214
|
}
|
|
170
215
|
const uploadedFiles = results
|
|
171
216
|
.filter((r) => r.status === "fulfilled")
|
|
172
217
|
.map((r) => r.value);
|
|
173
218
|
return {
|
|
174
219
|
uploadedFiles,
|
|
220
|
+
failedFiles: [],
|
|
175
221
|
totalFiles: filesToUpload.length,
|
|
176
222
|
totalSize,
|
|
177
223
|
uploadTime,
|
|
@@ -185,7 +231,8 @@ export class PlaywrightReporterStorageManager {
|
|
|
185
231
|
return fileInfo.relativePath;
|
|
186
232
|
}
|
|
187
233
|
catch (error) {
|
|
188
|
-
|
|
234
|
+
coreLogger.error(`Failed to upload file: ${fileInfo.relativePath} - ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
235
|
+
throw error;
|
|
189
236
|
}
|
|
190
237
|
});
|
|
191
238
|
return this.executeWithOptimizedBatching(uploadTasks, concurrency);
|
|
@@ -217,7 +264,8 @@ export class PlaywrightReporterStorageManager {
|
|
|
217
264
|
}
|
|
218
265
|
catch (error) {
|
|
219
266
|
results[globalIndex] = { status: "rejected", reason: error };
|
|
220
|
-
|
|
267
|
+
coreLogger.error(`Task failed at index ${globalIndex}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
268
|
+
return;
|
|
221
269
|
}
|
|
222
270
|
});
|
|
223
271
|
const executing = [];
|
|
@@ -263,8 +311,11 @@ export class PlaywrightReporterStorageManager {
|
|
|
263
311
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
264
312
|
coreLogger.info(`Upload attempt ${attempt} failed for ${fileInfo.relativePath}: ${errorMessage}`);
|
|
265
313
|
if (isLastAttempt) {
|
|
266
|
-
coreLogger.error(`All retry attempts exhausted for ${fileInfo.relativePath}`);
|
|
267
|
-
|
|
314
|
+
coreLogger.error(`All retry attempts exhausted for ${fileInfo.relativePath}: ${errorMessage}`);
|
|
315
|
+
if (error instanceof Error) {
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
throw new Error(`Upload failed for ${fileInfo.relativePath} after ${maxRetries} attempts: ${errorMessage}`);
|
|
268
319
|
}
|
|
269
320
|
// Exponential backoff with jitter (Azure SDK pattern)
|
|
270
321
|
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
|