@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
|
@@ -13,6 +13,7 @@ const playwrightServiceApicall_js_1 = require("../utils/playwrightServiceApicall
|
|
|
13
13
|
class PlaywrightReporter {
|
|
14
14
|
config;
|
|
15
15
|
workspaceMetadata = null;
|
|
16
|
+
isReportingEnabled = false;
|
|
16
17
|
/**
|
|
17
18
|
* Called when test run begins. Stores configuration for later use and validates serviceAuthType and
|
|
18
19
|
* retrieves workspace metadata.
|
|
@@ -20,34 +21,72 @@ class PlaywrightReporter {
|
|
|
20
21
|
*/
|
|
21
22
|
async onBegin(config) {
|
|
22
23
|
logger_js_1.coreLogger.info(`Reporter configuration: ${JSON.stringify(config.reporter, null, 2)}`);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Validate that reporter is used only with ENTRA_ID authentication
|
|
24
|
+
this.config = config;
|
|
25
|
+
// Check authentication
|
|
26
26
|
const playwrightServiceConfig = playwrightServiceConfig_js_1.PlaywrightServiceConfig.instance;
|
|
27
27
|
logger_js_1.coreLogger.info(`Current authentication type: ${playwrightServiceConfig.serviceAuthType}`);
|
|
28
28
|
const isUsingAccessToken = playwrightServiceConfig.serviceAuthType === constants_js_1.ServiceAuth.ACCESS_TOKEN;
|
|
29
29
|
if (isUsingAccessToken) {
|
|
30
|
-
|
|
30
|
+
console.error(messages_js_1.ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH.message);
|
|
31
|
+
this.isReportingEnabled = false;
|
|
32
|
+
return;
|
|
31
33
|
}
|
|
32
34
|
logger_js_1.coreLogger.info("Authentication validation passed - using ENTRA_ID");
|
|
33
|
-
this.config = config;
|
|
34
35
|
// Get workspace metadata for later use
|
|
35
36
|
try {
|
|
36
37
|
const playwrightServiceApiClient = new playwrightServiceApicall_js_1.PlaywrightServiceApiCall();
|
|
37
38
|
this.workspaceMetadata = await playwrightServiceApiClient.getWorkspaceMetadata();
|
|
38
|
-
|
|
39
|
+
if (!this.workspaceMetadata?.storageUri) {
|
|
40
|
+
console.error(messages_js_1.ServiceErrorMessageConstants.WORKSPACE_REPORTING_DISABLED.message);
|
|
41
|
+
console.error(messages_js_1.ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);
|
|
42
|
+
this.isReportingEnabled = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.isReportingEnabled = true;
|
|
46
|
+
this.validateHtmlReporterConfiguration(config);
|
|
47
|
+
if (this.isReportingEnabled) {
|
|
48
|
+
console.log(messages_js_1.ServiceErrorMessageConstants.REPORTING_ENABLED.message);
|
|
49
|
+
}
|
|
39
50
|
}
|
|
40
51
|
catch (error) {
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
53
|
+
console.error(`${messages_js_1.ServiceErrorMessageConstants.WORKSPACE_METADATA_FETCH_FAILED.message}Error: ${errorMessage} `);
|
|
54
|
+
this.isReportingEnabled = false;
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
57
|
/**
|
|
46
|
-
* Called when test run ends. Uploads Playwright test report to Azure Storage.
|
|
58
|
+
* Called when test run ends. Uploads Playwright test report to Azure Storage if reporting is enabled.
|
|
47
59
|
*/
|
|
48
60
|
async onEnd() {
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
if (this.isReportingEnabled) {
|
|
62
|
+
console.log(messages_js_1.ServiceErrorMessageConstants.COLLECTING_ARTIFACTS.message);
|
|
63
|
+
const uploadResult = await this.uploadHtmlReport();
|
|
64
|
+
if (uploadResult.success) {
|
|
65
|
+
if (uploadResult.partialSuccess &&
|
|
66
|
+
uploadResult.failedFileDetails &&
|
|
67
|
+
uploadResult.failedFileDetails.length > 0) {
|
|
68
|
+
console.log("Warning: Failed to upload the following files:");
|
|
69
|
+
uploadResult.failedFileDetails.forEach((fileDetail) => {
|
|
70
|
+
console.log(` - ${fileDetail.fileName}, ERROR: ${fileDetail.error}`);
|
|
71
|
+
});
|
|
72
|
+
console.log(messages_js_1.ServiceErrorMessageConstants.REPORTING_STATUS_PARTIAL.message);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(messages_js_1.ServiceErrorMessageConstants.REPORTING_STATUS_SUCCESS.message);
|
|
76
|
+
}
|
|
77
|
+
// Display portal URL for both full and partial success
|
|
78
|
+
if (this.workspaceMetadata) {
|
|
79
|
+
const portalUrl = (0, utils_js_1.getPortalTestRunUrl)(this.workspaceMetadata);
|
|
80
|
+
console.log(messages_js_1.ServiceErrorMessageConstants.TEST_REPORT_VIEW_URL.formatWithUrl(portalUrl));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.error(messages_js_1.ServiceErrorMessageConstants.REPORTING_STATUS_FAILED.message);
|
|
85
|
+
if (uploadResult.errorMessage) {
|
|
86
|
+
console.error(`Error: ${uploadResult.errorMessage}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
51
90
|
}
|
|
52
91
|
async uploadHtmlReport() {
|
|
53
92
|
try {
|
|
@@ -55,22 +94,23 @@ class PlaywrightReporter {
|
|
|
55
94
|
logger_js_1.coreLogger.info(`Resolved Playwright test report output folder: ${outputFolder}`);
|
|
56
95
|
const storageManager = new playwrightReporterStorageManager_js_1.PlaywrightReporterStorageManager();
|
|
57
96
|
logger_js_1.coreLogger.info("Starting Playwright test report upload process");
|
|
58
|
-
await storageManager.uploadPlaywrightHtmlReportAfterTests(outputFolder, this.workspaceMetadata);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (this.workspaceMetadata) {
|
|
62
|
-
const portalUrl = (0, utils_js_1.getPortalTestRunUrl)(this.workspaceMetadata);
|
|
63
|
-
process.stdout.write(`You can view test report at: ${portalUrl}\n`);
|
|
97
|
+
const uploadResult = await storageManager.uploadPlaywrightHtmlReportAfterTests(outputFolder, this.workspaceMetadata);
|
|
98
|
+
if (uploadResult.success) {
|
|
99
|
+
logger_js_1.coreLogger.info(`Playwright Test report uploaded successfully to Azure Storage.`);
|
|
64
100
|
}
|
|
101
|
+
return uploadResult;
|
|
65
102
|
}
|
|
66
103
|
catch (error) {
|
|
67
104
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
68
|
-
|
|
105
|
+
logger_js_1.coreLogger.error(`Upload failed: ${errorMessage}`);
|
|
106
|
+
return { success: false, errorMessage };
|
|
69
107
|
}
|
|
70
108
|
}
|
|
71
109
|
validateHtmlReporterConfiguration(config) {
|
|
72
110
|
if (!config.reporter || !Array.isArray(config.reporter)) {
|
|
73
|
-
|
|
111
|
+
console.error(messages_js_1.ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
|
|
112
|
+
this.isReportingEnabled = false;
|
|
113
|
+
return;
|
|
74
114
|
}
|
|
75
115
|
const hasHtmlReporter = config.reporter.some((reporter) => {
|
|
76
116
|
if (typeof reporter === "string") {
|
|
@@ -82,7 +122,9 @@ class PlaywrightReporter {
|
|
|
82
122
|
return false;
|
|
83
123
|
});
|
|
84
124
|
if (!hasHtmlReporter) {
|
|
85
|
-
|
|
125
|
+
console.error(messages_js_1.ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
|
|
126
|
+
this.isReportingEnabled = false;
|
|
127
|
+
return;
|
|
86
128
|
}
|
|
87
129
|
logger_js_1.coreLogger.info("HTML reporter validation passed - HTML reporter is configured");
|
|
88
130
|
}
|
|
@@ -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":"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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 { 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"}
|
|
@@ -12,141 +12,159 @@ const messages_js_1 = require("../common/messages.js");
|
|
|
12
12
|
const utils_js_1 = require("./utils.js");
|
|
13
13
|
const playwrightServiceConfig_js_1 = require("../common/playwrightServiceConfig.js");
|
|
14
14
|
class PlaywrightReporterStorageManager {
|
|
15
|
+
// Uploads the HTML report folder to Azure Storage
|
|
15
16
|
async uploadHtmlReportFolder(credential, runId, outputFolder, workspaceDetails) {
|
|
16
17
|
logger_js_1.coreLogger.info(`Starting HTML report upload for runId: ${runId}, outputFolder: ${outputFolder}`);
|
|
17
18
|
try {
|
|
18
19
|
logger_js_1.coreLogger.info(`Received workspace details: ${JSON.stringify(workspaceDetails, null, 2)}`);
|
|
19
20
|
if (!workspaceDetails.storageUri) {
|
|
20
|
-
|
|
21
|
+
logger_js_1.coreLogger.error("Storage URI not found in workspace details");
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
errorMessage: messages_js_1.ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message,
|
|
25
|
+
};
|
|
21
26
|
}
|
|
22
|
-
logger_js_1.coreLogger.info(`Extracting storage account from URI: ${workspaceDetails.storageUri}`);
|
|
23
27
|
const blobServiceClient = new storage_blob_1.BlobServiceClient(workspaceDetails?.storageUri, credential);
|
|
24
28
|
const serviceUrlInfo = (0, utils_js_1.populateValuesFromServiceUrl)();
|
|
25
29
|
if (!serviceUrlInfo?.accountId) {
|
|
26
|
-
|
|
30
|
+
logger_js_1.coreLogger.error("Unable to extract workspace ID from service URL");
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
errorMessage: messages_js_1.ServiceErrorMessageConstants.UNABLE_TO_EXTRACT_WORKSPACE_ID.message,
|
|
34
|
+
};
|
|
27
35
|
}
|
|
28
36
|
const containerName = serviceUrlInfo.accountId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
29
37
|
const containerClient = blobServiceClient.getContainerClient(containerName);
|
|
30
38
|
const containerExists = await containerClient.exists();
|
|
31
39
|
if (!containerExists) {
|
|
32
40
|
await containerClient.create();
|
|
33
|
-
console.log(`Created new container for this workspace: ${containerName}`);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
console.log(`Using existing container for this workspace: ${containerName}`);
|
|
37
41
|
}
|
|
38
42
|
const folderName = runId;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
await this.
|
|
42
|
-
await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
|
|
43
|
-
|
|
43
|
+
const storageAccountName = (0, utils_js_1.getStorageAccountNameFromUri)(workspaceDetails?.storageUri || "") || "unknown";
|
|
44
|
+
console.log(messages_js_1.ServiceErrorMessageConstants.UPLOADING_ARTIFACTS.formatWithDetails(storageAccountName, containerName, folderName));
|
|
45
|
+
await this.modifyTraceIndexHtml(outputFolder);
|
|
46
|
+
const uploadResults = await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
|
|
47
|
+
if (uploadResults.totalFiles === 0) {
|
|
48
|
+
return { success: false, errorMessage: "No files found to upload" };
|
|
49
|
+
}
|
|
50
|
+
const failedFiles = uploadResults.totalFiles - uploadResults.uploadedFiles.length;
|
|
51
|
+
if (failedFiles > 0) {
|
|
52
|
+
// Get list of failed file names by comparing total files with uploaded files
|
|
53
|
+
const uploadedSet = new Set(uploadResults.uploadedFiles);
|
|
54
|
+
const allFiles = (0, utils_js_1.collectAllFiles)(outputFolder, outputFolder, folderName);
|
|
55
|
+
const failedFileNames = allFiles
|
|
56
|
+
.filter((file) => !uploadedSet.has(file.relativePath))
|
|
57
|
+
.map((file) => file.relativePath);
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
partialSuccess: true,
|
|
61
|
+
failedFileCount: failedFiles,
|
|
62
|
+
totalFiles: uploadResults.totalFiles,
|
|
63
|
+
failedFiles: failedFileNames,
|
|
64
|
+
failedFileDetails: uploadResults.failedFileDetails,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return { success: true };
|
|
44
68
|
}
|
|
45
69
|
catch (error) {
|
|
46
70
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
47
|
-
logger_js_1.coreLogger.error(`Failed to upload HTML report: ${
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logger_js_1.coreLogger.info(`Resolving sasworker.js path: ${sasWorkerPath}`);
|
|
56
|
-
if (!(0, fs_1.existsSync)(sasWorkerPath)) {
|
|
57
|
-
logger_js_1.coreLogger.error(`sasworker.js file not found at path: ${sasWorkerPath}`);
|
|
58
|
-
return;
|
|
71
|
+
logger_js_1.coreLogger.error(`Failed to upload HTML report: ${error}`);
|
|
72
|
+
if (error instanceof Error &&
|
|
73
|
+
(error.message.includes("AuthorizationFailure") ||
|
|
74
|
+
error.message.includes("not authorized to perform this operation"))) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
errorMessage: messages_js_1.ServiceErrorMessageConstants.STORAGE_AUTHORIZATION_FAILED.message,
|
|
78
|
+
};
|
|
59
79
|
}
|
|
60
|
-
|
|
61
|
-
const fileContent = (0, fs_1.readFileSync)(sasWorkerPath);
|
|
62
|
-
const blobName = `${folderName}/sasworker.js`;
|
|
63
|
-
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
|
|
64
|
-
await blockBlobClient.upload(fileContent, fileContent.length, {
|
|
65
|
-
blobHTTPHeaders: {
|
|
66
|
-
blobContentType: "application/javascript",
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
logger_js_1.coreLogger.info("✅ Uploaded service worker file as sasworker.js");
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
logger_js_1.coreLogger.error(`Error uploading service worker file: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
-
throw error;
|
|
80
|
+
return { success: false, errorMessage };
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
|
-
async
|
|
77
|
-
logger_js_1.coreLogger.info(`Starting
|
|
78
|
-
const indexPath = (0, path_1.join)(outputFolder, "index.html");
|
|
83
|
+
async modifyTraceIndexHtml(outputFolder) {
|
|
84
|
+
logger_js_1.coreLogger.info(`Starting trace modification for folder: ${outputFolder}`);
|
|
85
|
+
const indexPath = (0, path_1.join)(outputFolder, "trace/index.html");
|
|
79
86
|
if (!(0, fs_1.existsSync)(indexPath)) {
|
|
80
|
-
logger_js_1.coreLogger.error(`index.html not found at path: ${indexPath}`);
|
|
87
|
+
logger_js_1.coreLogger.error(`trace/index.html not found at path: ${indexPath}`);
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
|
-
logger_js_1.coreLogger.info(`Found index.html at: ${indexPath}`);
|
|
90
|
+
logger_js_1.coreLogger.info(`Found trace/index.html at: ${indexPath}`);
|
|
84
91
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
const redirectTraceviewerScript = `<!DOCTYPE html>
|
|
93
|
+
<html>
|
|
94
|
+
<head>
|
|
95
|
+
<meta charset="utf-8">
|
|
96
|
+
<title>Redirecting to Trace Viewer...</title>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
88
99
|
<script>
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
const url = new URL(location.href);
|
|
101
|
+
const traceParam = url.searchParams.get('trace');
|
|
102
|
+
const trace = new URL(traceParam);
|
|
91
103
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!navigator.serviceWorker.controller && !sessionStorage.getItem('__sw_bootstrap__')) {
|
|
98
|
-
sessionStorage.setItem('__sw_bootstrap__', '1');
|
|
99
|
-
location.reload();
|
|
104
|
+
// Copy all query parameters from the current URL to the trace URL
|
|
105
|
+
// This includes SAS tokens (sv, sr, sig, etc.) that are on the main URL
|
|
106
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
107
|
+
if (key !== 'trace') {
|
|
108
|
+
trace.searchParams.set(key, value);
|
|
100
109
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
logger_js_1.coreLogger.info("Successfully wrote modified HTML content to file");
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
logger_js_1.coreLogger.error("Title tag not found in index.html - unable to inject service worker script");
|
|
116
|
-
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const publicTraceViewer = new URL('https://trace.playwright.dev/');
|
|
113
|
+
publicTraceViewer.searchParams.set('trace', trace.toString());
|
|
114
|
+
location.href = publicTraceViewer.toString();
|
|
115
|
+
</script>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
`;
|
|
119
|
+
(0, fs_1.writeFileSync)(indexPath, redirectTraceviewerScript, "utf-8");
|
|
120
|
+
logger_js_1.coreLogger.info("Successfully updated TraceViewer index file");
|
|
117
121
|
}
|
|
118
122
|
catch (error) {
|
|
119
|
-
logger_js_1.coreLogger.error(`Error modifying index.html: ${error instanceof Error ? error.message : String(error)}`);
|
|
120
|
-
|
|
123
|
+
logger_js_1.coreLogger.error(`Error modifying trace/index.html: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
return;
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
// Uploads the entire Playwright HTML report folder after tests complete.
|
|
124
128
|
async uploadPlaywrightHtmlReportAfterTests(outputFolderName, workspaceMetadata) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
try {
|
|
130
|
+
logger_js_1.coreLogger.info(`Starting post-test HTML report upload, folder name: ${outputFolderName || "default (playwright-report)"}`);
|
|
131
|
+
const cred = playwrightServiceConfig_js_1.PlaywrightServiceConfig.instance.credential;
|
|
132
|
+
if (!cred) {
|
|
133
|
+
logger_js_1.coreLogger.error("No credential found for authentication");
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
errorMessage: messages_js_1.ServiceErrorMessageConstants.NO_CRED_ENTRA_AUTH_ERROR.message,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
logger_js_1.coreLogger.info("Credential found for authentication");
|
|
140
|
+
const folderName = outputFolderName || "playwright-report";
|
|
141
|
+
const outputFolderPath = (0, path_1.join)(process.cwd(), folderName);
|
|
142
|
+
if (!(0, fs_1.existsSync)(outputFolderPath)) {
|
|
143
|
+
logger_js_1.coreLogger.error(`HTML report folder not found: ${outputFolderPath}`);
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
errorMessage: messages_js_1.ServiceErrorMessageConstants.PLAYWRIGHT_TEST_REPORT_NOT_FOUND.formatWithFolder(folderName),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
logger_js_1.coreLogger.info(`HTML report folder found: ${outputFolderPath}`);
|
|
150
|
+
const testRunId = playwrightServiceConfig_js_1.PlaywrightServiceConfig.instance.runId;
|
|
151
|
+
logger_js_1.coreLogger.info(`Starting upload for test run ID: ${testRunId}`);
|
|
152
|
+
const result = await this.uploadHtmlReportFolder(cred, testRunId, outputFolderPath, workspaceMetadata);
|
|
153
|
+
logger_js_1.coreLogger.info(`Completed upload for test run ID: ${testRunId}`);
|
|
154
|
+
return result;
|
|
130
155
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
logger_js_1.coreLogger.error(`HTML report folder not found: ${outputFolderPath}`);
|
|
136
|
-
throw new Error(messages_js_1.ServiceErrorMessageConstants.HTML_REPORT_FOLDER_NOT_FOUND.formatWithFolder(folderName));
|
|
156
|
+
catch (error) {
|
|
157
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
158
|
+
logger_js_1.coreLogger.error(`Upload failed: ${errorMessage}`);
|
|
159
|
+
return { success: false, errorMessage };
|
|
137
160
|
}
|
|
138
|
-
logger_js_1.coreLogger.info(`HTML report folder found: ${outputFolderPath}`);
|
|
139
|
-
const testRunId = playwrightServiceConfig_js_1.PlaywrightServiceConfig.instance.runId;
|
|
140
|
-
logger_js_1.coreLogger.info(`Starting upload for test run ID: ${testRunId}`);
|
|
141
|
-
await this.uploadHtmlReportFolder(cred, testRunId, outputFolderPath, workspaceMetadata);
|
|
142
|
-
logger_js_1.coreLogger.info(`Completed upload for test run ID: ${testRunId}`);
|
|
143
161
|
}
|
|
144
162
|
// Parallel Upload Engine - Core upload orchestration with performance optimization
|
|
145
163
|
async uploadFolderInParallel(containerClient, folderPath, basePath, runIdFolderPrefix) {
|
|
146
164
|
// Sort by size descending - upload large files first for better parallelization
|
|
147
165
|
const filesToUpload = (0, utils_js_1.collectAllFiles)(folderPath, basePath, runIdFolderPrefix).sort((a, b) => b.size - a.size);
|
|
148
166
|
if (filesToUpload.length === 0) {
|
|
149
|
-
return { uploadedFiles: [], totalFiles: 0, totalSize: 0, uploadTime: 0 };
|
|
167
|
+
return { uploadedFiles: [], failedFiles: [], totalFiles: 0, totalSize: 0, uploadTime: 0 };
|
|
150
168
|
}
|
|
151
169
|
const totalSize = filesToUpload.reduce((sum, file) => sum + file.size, 0);
|
|
152
170
|
const concurrency = (0, utils_js_1.calculateOptimalConcurrency)(filesToUpload);
|
|
@@ -168,13 +186,41 @@ class PlaywrightReporterStorageManager {
|
|
|
168
186
|
errors.forEach((error, index) => {
|
|
169
187
|
logger_js_1.coreLogger.error(` ${index + 1}. ${error}`);
|
|
170
188
|
});
|
|
171
|
-
|
|
189
|
+
// Get failed file names and their error messages
|
|
190
|
+
const uploadedSet = new Set(results
|
|
191
|
+
.filter((r) => r.status === "fulfilled")
|
|
192
|
+
.map((r) => r.value));
|
|
193
|
+
const failedFileNames = filesToUpload
|
|
194
|
+
.filter((file) => !uploadedSet.has(file.relativePath))
|
|
195
|
+
.map((file) => file.relativePath);
|
|
196
|
+
// Create detailed error mapping
|
|
197
|
+
const failedFileDetails = [];
|
|
198
|
+
results.forEach((result, index) => {
|
|
199
|
+
if (result.status === "rejected") {
|
|
200
|
+
const fileName = filesToUpload[index]?.relativePath || `File at index ${index}`;
|
|
201
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
202
|
+
failedFileDetails.push({ fileName, error: errorMessage });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
// Log error but don't throw to prevent breaking HTML reporter
|
|
206
|
+
logger_js_1.coreLogger.error(`Upload failed: ${failed} files could not be uploaded. Sample errors: ${errors.join(", ")}`);
|
|
207
|
+
return {
|
|
208
|
+
uploadedFiles: results
|
|
209
|
+
.filter((r) => r.status === "fulfilled")
|
|
210
|
+
.map((r) => r.value),
|
|
211
|
+
failedFiles: failedFileNames,
|
|
212
|
+
failedFileDetails,
|
|
213
|
+
totalFiles: filesToUpload.length,
|
|
214
|
+
totalSize,
|
|
215
|
+
uploadTime,
|
|
216
|
+
};
|
|
172
217
|
}
|
|
173
218
|
const uploadedFiles = results
|
|
174
219
|
.filter((r) => r.status === "fulfilled")
|
|
175
220
|
.map((r) => r.value);
|
|
176
221
|
return {
|
|
177
222
|
uploadedFiles,
|
|
223
|
+
failedFiles: [],
|
|
178
224
|
totalFiles: filesToUpload.length,
|
|
179
225
|
totalSize,
|
|
180
226
|
uploadTime,
|
|
@@ -188,7 +234,8 @@ class PlaywrightReporterStorageManager {
|
|
|
188
234
|
return fileInfo.relativePath;
|
|
189
235
|
}
|
|
190
236
|
catch (error) {
|
|
191
|
-
|
|
237
|
+
logger_js_1.coreLogger.error(`Failed to upload file: ${fileInfo.relativePath} - ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
238
|
+
throw error;
|
|
192
239
|
}
|
|
193
240
|
});
|
|
194
241
|
return this.executeWithOptimizedBatching(uploadTasks, concurrency);
|
|
@@ -220,7 +267,8 @@ class PlaywrightReporterStorageManager {
|
|
|
220
267
|
}
|
|
221
268
|
catch (error) {
|
|
222
269
|
results[globalIndex] = { status: "rejected", reason: error };
|
|
223
|
-
|
|
270
|
+
logger_js_1.coreLogger.error(`Task failed at index ${globalIndex}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
271
|
+
return;
|
|
224
272
|
}
|
|
225
273
|
});
|
|
226
274
|
const executing = [];
|
|
@@ -266,8 +314,11 @@ class PlaywrightReporterStorageManager {
|
|
|
266
314
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
267
315
|
logger_js_1.coreLogger.info(`Upload attempt ${attempt} failed for ${fileInfo.relativePath}: ${errorMessage}`);
|
|
268
316
|
if (isLastAttempt) {
|
|
269
|
-
logger_js_1.coreLogger.error(`All retry attempts exhausted for ${fileInfo.relativePath}`);
|
|
270
|
-
|
|
317
|
+
logger_js_1.coreLogger.error(`All retry attempts exhausted for ${fileInfo.relativePath}: ${errorMessage}`);
|
|
318
|
+
if (error instanceof Error) {
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
throw new Error(`Upload failed for ${fileInfo.relativePath} after ${maxRetries} attempts: ${errorMessage}`);
|
|
271
322
|
}
|
|
272
323
|
// Exponential backoff with jitter (Azure SDK pattern)
|
|
273
324
|
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
|