@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.
Files changed (89) hide show
  1. package/dist/browser/common/messages.d.ts +46 -9
  2. package/dist/browser/common/messages.d.ts.map +1 -1
  3. package/dist/browser/common/messages.js +59 -22
  4. package/dist/browser/common/messages.js.map +1 -1
  5. package/dist/browser/common/types.d.ts +12 -0
  6. package/dist/browser/common/types.d.ts.map +1 -1
  7. package/dist/browser/common/types.js.map +1 -1
  8. package/dist/browser/reporter/playwrightReporter.d.ts +2 -1
  9. package/dist/browser/reporter/playwrightReporter.d.ts.map +1 -1
  10. package/dist/browser/reporter/playwrightReporter.js +63 -21
  11. package/dist/browser/reporter/playwrightReporter.js.map +1 -1
  12. package/dist/browser/utils/playwrightReporterStorageManager.d.ts +4 -5
  13. package/dist/browser/utils/playwrightReporterStorageManager.d.ts.map +1 -1
  14. package/dist/browser/utils/playwrightReporterStorageManager.js +149 -98
  15. package/dist/browser/utils/playwrightReporterStorageManager.js.map +1 -1
  16. package/dist/browser/utils/playwrightServiceApicall.d.ts.map +1 -1
  17. package/dist/browser/utils/playwrightServiceApicall.js +44 -30
  18. package/dist/browser/utils/playwrightServiceApicall.js.map +1 -1
  19. package/dist/browser/utils/utils.d.ts +1 -0
  20. package/dist/browser/utils/utils.d.ts.map +1 -1
  21. package/dist/browser/utils/utils.js +19 -0
  22. package/dist/browser/utils/utils.js.map +1 -1
  23. package/dist/commonjs/common/messages.d.ts +46 -9
  24. package/dist/commonjs/common/messages.d.ts.map +1 -1
  25. package/dist/commonjs/common/messages.js +59 -22
  26. package/dist/commonjs/common/messages.js.map +1 -1
  27. package/dist/commonjs/common/types.d.ts +12 -0
  28. package/dist/commonjs/common/types.d.ts.map +1 -1
  29. package/dist/commonjs/common/types.js.map +1 -1
  30. package/dist/commonjs/reporter/playwrightReporter.d.ts +2 -1
  31. package/dist/commonjs/reporter/playwrightReporter.d.ts.map +1 -1
  32. package/dist/commonjs/reporter/playwrightReporter.js +62 -20
  33. package/dist/commonjs/reporter/playwrightReporter.js.map +1 -1
  34. package/dist/commonjs/utils/playwrightReporterStorageManager.d.ts +4 -5
  35. package/dist/commonjs/utils/playwrightReporterStorageManager.d.ts.map +1 -1
  36. package/dist/commonjs/utils/playwrightReporterStorageManager.js +147 -96
  37. package/dist/commonjs/utils/playwrightReporterStorageManager.js.map +1 -1
  38. package/dist/commonjs/utils/playwrightServiceApicall.d.ts.map +1 -1
  39. package/dist/commonjs/utils/playwrightServiceApicall.js +43 -29
  40. package/dist/commonjs/utils/playwrightServiceApicall.js.map +1 -1
  41. package/dist/commonjs/utils/utils.d.ts +1 -0
  42. package/dist/commonjs/utils/utils.d.ts.map +1 -1
  43. package/dist/commonjs/utils/utils.js +21 -1
  44. package/dist/commonjs/utils/utils.js.map +1 -1
  45. package/dist/esm/common/messages.d.ts +46 -9
  46. package/dist/esm/common/messages.d.ts.map +1 -1
  47. package/dist/esm/common/messages.js +59 -22
  48. package/dist/esm/common/messages.js.map +1 -1
  49. package/dist/esm/common/types.d.ts +12 -0
  50. package/dist/esm/common/types.d.ts.map +1 -1
  51. package/dist/esm/common/types.js.map +1 -1
  52. package/dist/esm/reporter/playwrightReporter.d.ts +2 -1
  53. package/dist/esm/reporter/playwrightReporter.d.ts.map +1 -1
  54. package/dist/esm/reporter/playwrightReporter.js +63 -21
  55. package/dist/esm/reporter/playwrightReporter.js.map +1 -1
  56. package/dist/esm/utils/playwrightReporterStorageManager.d.ts +4 -5
  57. package/dist/esm/utils/playwrightReporterStorageManager.d.ts.map +1 -1
  58. package/dist/esm/utils/playwrightReporterStorageManager.js +149 -98
  59. package/dist/esm/utils/playwrightReporterStorageManager.js.map +1 -1
  60. package/dist/esm/utils/playwrightServiceApicall.d.ts.map +1 -1
  61. package/dist/esm/utils/playwrightServiceApicall.js +44 -30
  62. package/dist/esm/utils/playwrightServiceApicall.js.map +1 -1
  63. package/dist/esm/utils/utils.d.ts +1 -0
  64. package/dist/esm/utils/utils.d.ts.map +1 -1
  65. package/dist/esm/utils/utils.js +19 -0
  66. package/dist/esm/utils/utils.js.map +1 -1
  67. package/dist/react-native/common/messages.d.ts +46 -9
  68. package/dist/react-native/common/messages.d.ts.map +1 -1
  69. package/dist/react-native/common/messages.js +59 -22
  70. package/dist/react-native/common/messages.js.map +1 -1
  71. package/dist/react-native/common/types.d.ts +12 -0
  72. package/dist/react-native/common/types.d.ts.map +1 -1
  73. package/dist/react-native/common/types.js.map +1 -1
  74. package/dist/react-native/reporter/playwrightReporter.d.ts +2 -1
  75. package/dist/react-native/reporter/playwrightReporter.d.ts.map +1 -1
  76. package/dist/react-native/reporter/playwrightReporter.js +63 -21
  77. package/dist/react-native/reporter/playwrightReporter.js.map +1 -1
  78. package/dist/react-native/utils/playwrightReporterStorageManager.d.ts +4 -5
  79. package/dist/react-native/utils/playwrightReporterStorageManager.d.ts.map +1 -1
  80. package/dist/react-native/utils/playwrightReporterStorageManager.js +149 -98
  81. package/dist/react-native/utils/playwrightReporterStorageManager.js.map +1 -1
  82. package/dist/react-native/utils/playwrightServiceApicall.d.ts.map +1 -1
  83. package/dist/react-native/utils/playwrightServiceApicall.js +44 -30
  84. package/dist/react-native/utils/playwrightServiceApicall.js.map +1 -1
  85. package/dist/react-native/utils/utils.d.ts +1 -0
  86. package/dist/react-native/utils/utils.d.ts.map +1 -1
  87. package/dist/react-native/utils/utils.js +19 -0
  88. package/dist/react-native/utils/utils.js.map +1 -1
  89. 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
- // Validate that HTML reporter is configured
24
- this.validateHtmlReporterConfiguration(config);
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
- (0, utils_js_1.exitWithFailureMessage)(messages_js_1.ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH);
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
- process.stdout.write("Initializing reporting for this test run.\n");
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
- logger_js_1.coreLogger.error(`Failed to get workspace metadata: ${error instanceof Error ? error.message : String(error)}\n`);
42
- process.stderr.write("Failed to initialize reporting for this test run.\n");
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
- process.stdout.write(`Uploading Playwright Test report in Azure storage account.\n`);
50
- await this.uploadHtmlReport();
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
- logger_js_1.coreLogger.info(`✅ Playwright Test report uploaded successfully to Azure Storage.`);
60
- // Display portal URL for test report
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
- process.stderr.write(`❌ Failed to upload Playwright test report: ${errorMessage}\n`);
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
- throw new Error(messages_js_1.ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
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
- (0, utils_js_1.exitWithFailureMessage)(messages_js_1.ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED);
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<void>;
5
- private uploadSasWorkerFile;
6
- private modifyIndexHtml;
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;AAYxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,qBAAa,gCAAgC;IACrC,sBAAsB,CAC1B,UAAU,EAAE,eAAe,EAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,iBAAiB,GAClC,OAAO,CAAC,IAAI,CAAC;YA6CF,mBAAmB;YA+BnB,eAAe;IA2DvB,oCAAoC,CACxC,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAC3C,OAAO,CAAC,IAAI,CAAC;YA8BF,sBAAsB;YAyEtB,4BAA4B;YAqB5B,4BAA4B;YAuE5B,yBAAyB;YA4CzB,sBAAsB;CAsCrC"}
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
- throw new Error(messages_js_1.ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);
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
- throw new Error(messages_js_1.ServiceErrorMessageConstants.UNABLE_TO_EXTRACT_WORKSPACE_ID.message);
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
- console.log(`Folder created for this run: ${folderName}`);
40
- await this.modifyIndexHtml(outputFolder);
41
- await this.uploadSasWorkerFile(containerClient, folderName);
42
- await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
43
- console.log(`✅ Successfully uploaded Playwright report for run: ${runId} to Azure Storage.`);
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: ${errorMessage}`);
48
- throw new Error(messages_js_1.ServiceErrorMessageConstants.HTML_REPORT_UPLOAD_FAILED.formatWithError(errorMessage));
49
- }
50
- }
51
- async uploadSasWorkerFile(containerClient, folderName) {
52
- logger_js_1.coreLogger.info(`Starting sasworker.js upload for folder: ${folderName}`);
53
- try {
54
- const sasWorkerPath = (0, path_1.join)(process.cwd(), "sasworker.js");
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
- logger_js_1.coreLogger.info(`Found sasworker.js file at: ${sasWorkerPath}`);
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 modifyIndexHtml(outputFolder) {
77
- logger_js_1.coreLogger.info(`Starting HTML modification for folder: ${outputFolder}`);
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
- let htmlContent = (0, fs_1.readFileSync)(indexPath, "utf-8");
86
- // Service worker registration script
87
- const serviceWorkerScript = `
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
- (async () => {
90
- if (!('serviceWorker' in navigator)) return;
100
+ const url = new URL(location.href);
101
+ const traceParam = url.searchParams.get('trace');
102
+ const trace = new URL(traceParam);
91
103
 
92
- const sas = window.location.search;
93
- const swUrl = './sasworker.js' + sas;
94
- const scope = './';
95
- await navigator.serviceWorker.register(swUrl, { scope });
96
- await navigator.serviceWorker.ready;
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
- </script>`;
103
- const titleMatch = htmlContent.match(/<\/title>/i);
104
- if (titleMatch) {
105
- logger_js_1.coreLogger.info(`Found title tag, injecting service worker script at position: ${titleMatch.index + titleMatch[0].length}`);
106
- const insertPosition = titleMatch.index + titleMatch[0].length;
107
- htmlContent =
108
- htmlContent.slice(0, insertPosition) +
109
- serviceWorkerScript +
110
- htmlContent.slice(insertPosition);
111
- (0, fs_1.writeFileSync)(indexPath, htmlContent, "utf-8");
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
- throw error;
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
- logger_js_1.coreLogger.info(`Starting post-test HTML report upload, folder name: ${outputFolderName || "default (playwright-report)"}`);
126
- const cred = playwrightServiceConfig_js_1.PlaywrightServiceConfig.instance.credential;
127
- if (!cred) {
128
- logger_js_1.coreLogger.error("No credential found for authentication");
129
- throw new Error(messages_js_1.ServiceErrorMessageConstants.NO_CRED_ENTRA_AUTH_ERROR.message);
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
- logger_js_1.coreLogger.info("Credential found for authentication");
132
- const folderName = outputFolderName || "playwright-report";
133
- const outputFolderPath = (0, path_1.join)(process.cwd(), folderName);
134
- if (!(0, fs_1.existsSync)(outputFolderPath)) {
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
- throw new Error(messages_js_1.ServiceErrorMessageConstants.UPLOAD_FAILED_MULTIPLE_FILES.formatWithDetails(failed, errors));
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
- throw new Error(`${fileInfo.relativePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
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
- throw error;
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
- throw new Error(messages_js_1.ServiceErrorMessageConstants.UPLOAD_RETRY_EXHAUSTED.formatWithDetails(maxRetries, errorMessage));
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;