@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
@@ -1,5 +1,5 @@
1
1
  import { PlaywrightReporterStorageManager } from "../utils/playwrightReporterStorageManager.js";
2
- import { getHtmlReporterOutputFolder, getPortalTestRunUrl, exitWithFailureMessage, } from "../utils/utils.js";
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
- // Validate that HTML reporter is configured
22
- this.validateHtmlReporterConfiguration(config);
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
- exitWithFailureMessage(ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH);
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
- process.stdout.write("Initializing reporting for this test run.\n");
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
- coreLogger.error(`Failed to get workspace metadata: ${error instanceof Error ? error.message : String(error)}\n`);
40
- process.stderr.write("Failed to initialize reporting for this test run.\n");
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
- process.stdout.write(`Uploading Playwright Test report in Azure storage account.\n`);
48
- await this.uploadHtmlReport();
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
- coreLogger.info(`✅ Playwright Test report uploaded successfully to Azure Storage.`);
58
- // Display portal URL for test report
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
- process.stderr.write(`❌ Failed to upload Playwright test report: ${errorMessage}\n`);
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
- throw new Error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
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
- exitWithFailureMessage(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED);
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":"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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"}
@@ -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, existsSync, writeFileSync, createReadStream } from "fs";
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
- throw new Error(ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);
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
- throw new Error(ServiceErrorMessageConstants.UNABLE_TO_EXTRACT_WORKSPACE_ID.message);
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
- console.log(`Folder created for this run: ${folderName}`);
37
- await this.modifyIndexHtml(outputFolder);
38
- await this.uploadSasWorkerFile(containerClient, folderName);
39
- await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
40
- console.log(`✅ Successfully uploaded Playwright report for run: ${runId} to Azure Storage.`);
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: ${errorMessage}`);
45
- throw new Error(ServiceErrorMessageConstants.HTML_REPORT_UPLOAD_FAILED.formatWithError(errorMessage));
46
- }
47
- }
48
- async uploadSasWorkerFile(containerClient, folderName) {
49
- coreLogger.info(`Starting sasworker.js upload for folder: ${folderName}`);
50
- try {
51
- const sasWorkerPath = join(process.cwd(), "sasworker.js");
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
- coreLogger.info(`Found sasworker.js file at: ${sasWorkerPath}`);
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 modifyIndexHtml(outputFolder) {
74
- coreLogger.info(`Starting HTML modification for folder: ${outputFolder}`);
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
- let htmlContent = readFileSync(indexPath, "utf-8");
83
- // Service worker registration script
84
- const serviceWorkerScript = `
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
- (async () => {
87
- if (!('serviceWorker' in navigator)) return;
97
+ const url = new URL(location.href);
98
+ const traceParam = url.searchParams.get('trace');
99
+ const trace = new URL(traceParam);
88
100
 
89
- const sas = window.location.search;
90
- const swUrl = './sasworker.js' + sas;
91
- const scope = './';
92
- await navigator.serviceWorker.register(swUrl, { scope });
93
- await navigator.serviceWorker.ready;
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
- </script>`;
100
- const titleMatch = htmlContent.match(/<\/title>/i);
101
- if (titleMatch) {
102
- coreLogger.info(`Found title tag, injecting service worker script at position: ${titleMatch.index + titleMatch[0].length}`);
103
- const insertPosition = titleMatch.index + titleMatch[0].length;
104
- htmlContent =
105
- htmlContent.slice(0, insertPosition) +
106
- serviceWorkerScript +
107
- htmlContent.slice(insertPosition);
108
- writeFileSync(indexPath, htmlContent, "utf-8");
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
- throw error;
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
- coreLogger.info(`Starting post-test HTML report upload, folder name: ${outputFolderName || "default (playwright-report)"}`);
123
- const cred = PlaywrightServiceConfig.instance.credential;
124
- if (!cred) {
125
- coreLogger.error("No credential found for authentication");
126
- throw new Error(ServiceErrorMessageConstants.NO_CRED_ENTRA_AUTH_ERROR.message);
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
- coreLogger.info("Credential found for authentication");
129
- const folderName = outputFolderName || "playwright-report";
130
- const outputFolderPath = join(process.cwd(), folderName);
131
- if (!existsSync(outputFolderPath)) {
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
- throw new Error(ServiceErrorMessageConstants.UPLOAD_FAILED_MULTIPLE_FILES.formatWithDetails(failed, errors));
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
- throw new Error(`${fileInfo.relativePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
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
- throw error;
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
- throw new Error(ServiceErrorMessageConstants.UPLOAD_RETRY_EXHAUSTED.formatWithDetails(maxRetries, errorMessage));
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;