@empiricalrun/playwright-utils 0.10.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @empiricalrun/playwright-utils
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cff4d1c: feat: add third party captcha solver
8
+
9
+ ## 0.12.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 3c94c63: feat: EmailClient support for email otp logins
14
+
15
+ ### Patch Changes
16
+
17
+ - ab045ff: fix: set dynamic maxQueueSize and clearer error logs
18
+
19
+ ## 0.11.0
20
+
21
+ ### Minor Changes
22
+
23
+ - ae142a5: feat: queue uploads in reporter
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [ae142a5]
28
+ - @empiricalrun/r2-uploader@0.1.3
29
+
3
30
  ## 0.10.0
4
31
 
5
32
  ### Minor Changes
package/README.md CHANGED
@@ -1,3 +1,60 @@
1
1
  # playwright-utils
2
2
 
3
- Playwright utils for test code repos of our customers
3
+ Playwright utils for test code repos of our customers
4
+
5
+ ## Utilities
6
+
7
+ - Default config for Playwright projects
8
+ - Custom reporter
9
+ - Highlight user actions
10
+ - Custom fixture to wrap around that
11
+ - Vision capabilities
12
+ - `locator.query` API
13
+ - Captcha
14
+ - Email automation
15
+
16
+ ## Email automation
17
+
18
+ ### Example usage
19
+
20
+ #### Dynamic email
21
+
22
+ This dynamically generates a random email address that can
23
+ be used for the test (e.g. invite a new user).
24
+
25
+ ```ts
26
+ import { EmailClient } from "@empiricalrun/playwright-utils";
27
+
28
+ const client = new EmailClient();
29
+ const address = client.getAddress();
30
+
31
+ // Input the `address` in the application
32
+ // that sends the email.
33
+
34
+ // Get email received on the `address`
35
+ const email = await client.waitForEmail();
36
+ ```
37
+
38
+ #### Static email
39
+
40
+ This uses a known (static) email that can be used to login
41
+ into an application.
42
+
43
+ This needs an email id (e.g. `test-login-user`). The email id
44
+ is appended with the domain (managed internally) to get the full
45
+ email address.
46
+
47
+ ```ts
48
+ import { EmailClient } from "@empiricalrun/playwright-utils";
49
+
50
+ const emailId = `test-login-user`;
51
+
52
+ const client = new EmailClient({ emailId });
53
+ const address = client.getAddress(); // Returns full address with domain
54
+
55
+ // Get email received on the `address`
56
+ const email = await client.waitForEmail();
57
+
58
+ // Get login OTP
59
+ const loginCode = email.codes[0];
60
+ ```
@@ -0,0 +1,7 @@
1
+ import { Page } from "@playwright/test";
2
+ type CaptchaSolveOptions = {
3
+ provider: "2captcha" | "llm-vision";
4
+ };
5
+ export declare function solveRecaptcha(page: Page, options?: CaptchaSolveOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/captcha/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAKxC,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,UAAU,GAAG,YAAY,CAAC;CACrC,CAAC;AAMF,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAAiC,iBAe3C"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.solveRecaptcha = void 0;
7
+ const puppeteer_extra_plugin_recaptcha_1 = __importDefault(require("puppeteer-extra-plugin-recaptcha"));
8
+ const vision_1 = require("./vision");
9
+ const defaultOpts = {
10
+ provider: "2captcha",
11
+ };
12
+ async function solveRecaptcha(page, options = defaultOpts) {
13
+ const { provider } = options;
14
+ if (provider === "2captcha") {
15
+ const rcPlugin = (0, puppeteer_extra_plugin_recaptcha_1.default)({
16
+ provider: {
17
+ id: "2captcha",
18
+ token: process.env.TWOCAPTCHA_API_KEY,
19
+ },
20
+ visualFeedback: true,
21
+ });
22
+ await rcPlugin.solveRecaptchas(page);
23
+ }
24
+ else if (provider === "llm-vision") {
25
+ return (0, vision_1.solveRecaptcha)(page);
26
+ }
27
+ }
28
+ exports.solveRecaptcha = solveRecaptcha;
@@ -1,3 +1,3 @@
1
1
  import { Page } from "@playwright/test";
2
2
  export declare function solveRecaptcha(page: Page): Promise<void>;
3
- //# sourceMappingURL=captcha.d.ts.map
3
+ //# sourceMappingURL=vision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.d.ts","sourceRoot":"","sources":["../../src/captcha/vision.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAyExD,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,iBAiB9C"}
package/dist/email.d.ts CHANGED
@@ -8,11 +8,18 @@ export type Email = {
8
8
  text?: string;
9
9
  html?: string;
10
10
  links: Link[];
11
+ codes: string[];
12
+ };
13
+ type EmailClientOptions = {
14
+ timeout?: number;
15
+ emailId?: string;
11
16
  };
12
17
  export declare class EmailClient {
13
18
  address: string;
14
19
  client: MailosaurClient;
15
- constructor();
20
+ timeout: number;
21
+ serverId: string;
22
+ constructor(opts?: EmailClientOptions);
16
23
  getAddress(): string;
17
24
  waitForEmail(): Promise<Email | undefined>;
18
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/email.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,WAAW,CAAC;AAExC,KAAK,IAAI,GAAG;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC;AAIF,qBAAa,WAAW;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,eAAe,CAAC;;IAUxB,UAAU;IAIJ,YAAY,IAAI,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;CAqBjD"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/email.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,WAAW,CAAC;AAGxC,KAAK,IAAI,GAAG;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAIF,KAAK,kBAAkB,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,SAAc;gBAEV,IAAI,GAAE,kBAAuB;IAWzC,UAAU;IAIJ,YAAY,IAAI,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;CAkCjD"}
package/dist/email.js CHANGED
@@ -5,36 +5,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.EmailClient = void 0;
7
7
  const mailosaur_1 = __importDefault(require("mailosaur"));
8
- const serverId = "pnyrwq5o";
8
+ const DEFAULT_TIMEOUT = 30000;
9
9
  class EmailClient {
10
10
  address;
11
11
  client;
12
- constructor() {
13
- const randomString = [...Array(7)]
14
- .map(() => Math.random().toString(36)[2])
15
- .join("");
16
- this.address = `${randomString}@${serverId}.mailosaur.net`;
12
+ timeout;
13
+ serverId = "pnyrwq5o";
14
+ constructor(opts = {}) {
17
15
  this.client = new mailosaur_1.default("NAEZtp2DVibTCzCxdYkZzhG4hrxSlBrV");
16
+ const { timeout = DEFAULT_TIMEOUT, emailId: knownEmailId } = opts;
17
+ this.timeout = timeout;
18
+ const emailId = knownEmailId ||
19
+ [...Array(7)].map(() => Math.random().toString(36)[2]).join("");
20
+ const emailDomain = `${this.serverId}.mailosaur.net`;
21
+ this.address = `${emailId}@${emailDomain}`;
18
22
  }
19
23
  getAddress() {
20
24
  return this.address;
21
25
  }
22
26
  async waitForEmail() {
23
27
  try {
24
- const email = await this.client.messages.get(serverId, {
28
+ const email = await this.client.messages.get(this.serverId, {
25
29
  sentTo: this.address,
26
- }, { timeout: 30000 });
27
- if (email) {
28
- return {
29
- subject: email.subject,
30
- text: email.text?.body,
31
- html: email.html?.body,
32
- links: email.html?.links || [],
33
- };
34
- }
30
+ }, { timeout: this.timeout });
31
+ const codesAsString = (email.html?.codes || []).map((code) => code.value);
32
+ return {
33
+ subject: email.subject,
34
+ text: email.text?.body,
35
+ html: email.html?.body,
36
+ links: email.html?.links || [],
37
+ codes: codesAsString,
38
+ };
35
39
  }
36
40
  catch (error) {
37
- console.log("Error receiving email");
41
+ if (error.errorType) {
42
+ const mailosaurError = error;
43
+ if (mailosaurError.errorType === "authentication_error") {
44
+ throw new Error("Email provider error: Authentication error");
45
+ }
46
+ else if (mailosaurError.errorType === "search_timeout") {
47
+ throw new Error(`Email not received within ${this.timeout}ms`);
48
+ }
49
+ else {
50
+ console.error(`Mailosaur error: ${mailosaurError.httpResponseBody}`);
51
+ throw new Error(`Unknown error: ${mailosaurError.errorType}`);
52
+ }
53
+ }
54
+ throw error;
38
55
  }
39
56
  }
40
57
  }
@@ -40,7 +40,11 @@ declare class HtmlReporter implements ReporterV2 {
40
40
  private _port;
41
41
  private _host;
42
42
  private _testCount;
43
- private pendingUploads;
43
+ private uploadExecutionQueue;
44
+ private uploadWaitingQueue;
45
+ private uploadMaxQueueSize;
46
+ private processQueue;
47
+ private waitForAllTasksToFinish;
44
48
  private _buildResult;
45
49
  private _topLevelErrors;
46
50
  constructor(options: HtmlReporterOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/reporter/custom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EAEV,KAAK,EACL,QAAQ,IAAI,cAAc,EAC1B,SAAS,EACT,UAAU,IAAI,gBAAgB,EAE/B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAML,UAAU,EAMX,MAAM,2BAA2B,CAAC;AAgBnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiC/C,QAAA,MAAM,iBAAiB,UAAoC,CAAC;AAC5D,KAAK,oBAAoB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAM/D,KAAK,mBAAmB,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,cAAM,YAAa,YAAW,UAAU;IACtC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAsB;IAE5C,OAAO,CAAC,YAAY,CAEN;IACd,OAAO,CAAC,eAAe,CAAmB;gBAE9B,OAAO,EAAE,mBAAmB;IAIxC,aAAa;IAIb,QAAQ;IAER,QAAQ;IAER,WAAW;IAEX,SAAS;IAET,OAAO,IAAI,IAAI;IAIf,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAQvC,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,gBAAgB;IAyCxD,WAAW,CAAC,MAAM,EAAE,UAAU;IAI9B,OAAO,CAAC,KAAK,EAAE,KAAK;IAYpB,eAAe,IAAI;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,oBAAoB,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;KAC1B;IAsBD,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IASxD,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAIzB,KAAK,CAAC,MAAM,EAAE,UAAU;IAqFxB,MAAM;CA8Bb;AAkCD,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,GAAE,MAAoB,EAC1B,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,iBAqBhB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAsBhE;AAqiBD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/reporter/custom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EAEV,KAAK,EACL,QAAQ,IAAI,cAAc,EAC1B,SAAS,EACT,UAAU,IAAI,gBAAgB,EAE/B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAML,UAAU,EAMX,MAAM,2BAA2B,CAAC;AAgBnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiC/C,QAAA,MAAM,iBAAiB,UAAoC,CAAC;AAC5D,KAAK,oBAAoB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAM/D,KAAK,mBAAmB,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,cAAM,YAAa,YAAW,UAAU;IACtC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,oBAAoB,CAAiC;IAC7D,OAAO,CAAC,kBAAkB,CAAkC;IAC5D,OAAO,CAAC,kBAAkB,CAEpB;IAEN,OAAO,CAAC,YAAY;YA8BN,uBAAuB;IAQrC,OAAO,CAAC,YAAY,CAEN;IACd,OAAO,CAAC,eAAe,CAAmB;gBAE9B,OAAO,EAAE,mBAAmB;IAIxC,aAAa;IAIb,QAAQ;IAER,QAAQ;IAER,WAAW;IAEX,SAAS;IAET,OAAO,IAAI,IAAI;IAIf,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAQvC,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,gBAAgB;IA4DxD,WAAW,CAAC,MAAM,EAAE,UAAU;IAI9B,OAAO,CAAC,KAAK,EAAE,KAAK;IAYpB,eAAe,IAAI;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,oBAAoB,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;KAC1B;IAsBD,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IASxD,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAIzB,KAAK,CAAC,MAAM,EAAE,UAAU;IA6FxB,MAAM;CA8Bb;AAkCD,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,GAAE,MAAoB,EAC1B,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,iBAqBhB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAsBhE;AAqiBD,eAAe,YAAY,CAAC"}
@@ -47,7 +47,44 @@ class HtmlReporter {
47
47
  _port;
48
48
  _host;
49
49
  _testCount = 1;
50
- pendingUploads = [];
50
+ uploadExecutionQueue = [];
51
+ uploadWaitingQueue = [];
52
+ uploadMaxQueueSize = process.env.UPLOAD_MAX_QUEUE_SIZE
53
+ ? parseInt(process.env.UPLOAD_MAX_QUEUE_SIZE)
54
+ : 2;
55
+ processQueue() {
56
+ try {
57
+ while (this.uploadExecutionQueue.length <= this.uploadMaxQueueSize &&
58
+ this.uploadWaitingQueue.length > 0) {
59
+ const task = this.uploadWaitingQueue.shift();
60
+ if (task) {
61
+ const promise = task()
62
+ .catch(() => {
63
+ // this.retryTask(task); // Retries handled within task itself
64
+ // console.log("Upload failed", e); // error is already logged in the upload function
65
+ })
66
+ .finally(() => {
67
+ // console.log("Finished upload execution from waiting queue"); // we already log in the upload function on successful upload
68
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
69
+ this.uploadExecutionQueue = this.uploadExecutionQueue.filter((p) => p !== promise);
70
+ this.processQueue();
71
+ });
72
+ console.log("Executing task from queue");
73
+ this.uploadExecutionQueue.push(promise);
74
+ }
75
+ }
76
+ }
77
+ catch (e) {
78
+ console.error("Error while processing queue", e);
79
+ }
80
+ }
81
+ async waitForAllTasksToFinish() {
82
+ return Promise.allSettled(this.uploadExecutionQueue).then(async () => {
83
+ if (this.uploadExecutionQueue.length > 0) {
84
+ await this.waitForAllTasksToFinish();
85
+ }
86
+ });
87
+ }
51
88
  _buildResult;
52
89
  _topLevelErrors = [];
53
90
  constructor(options) {
@@ -69,8 +106,6 @@ class HtmlReporter {
69
106
  this._testCount += 1;
70
107
  }
71
108
  onTestEnd(test, result) {
72
- // upload folder to R2
73
- // iterate over attachments
74
109
  try {
75
110
  result.attachments.forEach(async (attachment) => {
76
111
  if (!attachment.path)
@@ -83,8 +118,7 @@ class HtmlReporter {
83
118
  return;
84
119
  }
85
120
  try {
86
- const res = (0, r2_uploader_1.uploadDirectory)({
87
- // upload the exact file individually
121
+ const uploadTask = async () => (0, r2_uploader_1.uploadDirectory)({
88
122
  fileList: [attachment.path],
89
123
  sourceDir: this._outputFolder + "/data/" + folderName,
90
124
  destinationDir: process.env.PROJECT_NAME +
@@ -94,9 +128,29 @@ class HtmlReporter {
94
128
  folderName,
95
129
  uploadBucket: "test-report",
96
130
  });
97
- this.pendingUploads.push(res);
131
+ if (this.uploadExecutionQueue.length <= this.uploadMaxQueueSize) {
132
+ const promise = uploadTask()
133
+ .catch((e) => {
134
+ console.error("Upload failed for", attachment.path, e);
135
+ // this.retryTask(uploadTask); // Retry logic
136
+ })
137
+ .finally(() => {
138
+ // we already log in the upload function on successful upload
139
+ // console.log("Finished upload execution for ", attachment.path);
140
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
141
+ this.uploadExecutionQueue = this.uploadExecutionQueue.filter((p) => p !== promise);
142
+ this.processQueue(); // Keep processing queue
143
+ });
144
+ console.log("Executing upload task for ", attachment.path);
145
+ this.uploadExecutionQueue.push(promise);
146
+ }
147
+ else {
148
+ console.log("Queuing upload task ", attachment.path);
149
+ this.uploadWaitingQueue.push(uploadTask);
150
+ }
98
151
  }
99
152
  catch (e) {
153
+ console.log("Error while uploading attachment", e);
100
154
  // tests shouldn't stop no matter whatever error we get
101
155
  }
102
156
  });
@@ -160,35 +214,39 @@ class HtmlReporter {
160
214
  second: "2-digit",
161
215
  }).format(startTime));
162
216
  try {
163
- await Promise.allSettled([
164
- (0, r2_uploader_1.uploadDirectory)({
165
- sourceDir: this._outputFolder + "/trace",
166
- destinationDir: process.env.PROJECT_NAME +
167
- "/" +
168
- process.env.TEST_RUN_GITHUB_ACTION_ID +
169
- "/trace",
170
- uploadBucket: "test-report",
171
- }),
172
- // upload index.html
173
- (0, r2_uploader_1.uploadDirectory)({
174
- sourceDir: this._outputFolder,
175
- fileList: [path_1.default.join(this._outputFolder, "index.html")],
176
- destinationDir: process.env.PROJECT_NAME +
177
- "/" +
178
- process.env.TEST_RUN_GITHUB_ACTION_ID,
179
- uploadBucket: "test-report",
180
- }),
181
- // upload summary.json
182
- (0, r2_uploader_1.uploadDirectory)({
183
- sourceDir: this._outputFolder,
184
- fileList: [path_1.default.join(this._outputFolder, "summary.json")],
185
- destinationDir: process.env.PROJECT_NAME +
186
- "/" +
187
- process.env.TEST_RUN_GITHUB_ACTION_ID,
188
- uploadBucket: "test-report",
189
- }),
190
- ...this.pendingUploads,
191
- ]);
217
+ // await Promise.allSettled([
218
+ const uploadTraceTask = async () => (0, r2_uploader_1.uploadDirectory)({
219
+ sourceDir: this._outputFolder + "/trace",
220
+ destinationDir: process.env.PROJECT_NAME +
221
+ "/" +
222
+ process.env.TEST_RUN_GITHUB_ACTION_ID +
223
+ "/trace",
224
+ uploadBucket: "test-report",
225
+ });
226
+ // upload index.html
227
+ const uploadIndexTask = async () => (0, r2_uploader_1.uploadDirectory)({
228
+ sourceDir: this._outputFolder,
229
+ fileList: [path_1.default.join(this._outputFolder, "index.html")],
230
+ destinationDir: process.env.PROJECT_NAME +
231
+ "/" +
232
+ process.env.TEST_RUN_GITHUB_ACTION_ID,
233
+ uploadBucket: "test-report",
234
+ });
235
+ // upload summary.json
236
+ const uploadSummaryTask = async () => (0, r2_uploader_1.uploadDirectory)({
237
+ sourceDir: this._outputFolder,
238
+ fileList: [path_1.default.join(this._outputFolder, "summary.json")],
239
+ destinationDir: process.env.PROJECT_NAME +
240
+ "/" +
241
+ process.env.TEST_RUN_GITHUB_ACTION_ID,
242
+ uploadBucket: "test-report",
243
+ });
244
+ this.uploadWaitingQueue.push(uploadTraceTask);
245
+ this.uploadWaitingQueue.push(uploadIndexTask);
246
+ this.uploadWaitingQueue.push(uploadSummaryTask);
247
+ console.log("Waiting for all uploads to finish");
248
+ await this.waitForAllTasksToFinish();
249
+ console.log("All uploads finished");
192
250
  const endTime = new Date().getTime();
193
251
  console.log("Finished final report upload at: ", new Intl.DateTimeFormat("en-US", {
194
252
  hour: "2-digit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.10.0",
3
+ "version": "0.13.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -36,8 +36,10 @@
36
36
  "md5": "^2.3.0",
37
37
  "mime": "3.0.0",
38
38
  "playwright-core": "^1.46.1",
39
+ "playwright-extra": "^4.3.6",
40
+ "puppeteer-extra-plugin-recaptcha": "^3.6.8",
39
41
  "@empiricalrun/llm": "^0.9.1",
40
- "@empiricalrun/r2-uploader": "^0.1.2"
42
+ "@empiricalrun/r2-uploader": "^0.1.3"
41
43
  },
42
44
  "scripts": {
43
45
  "dev": "tsc --build --watch",
@@ -1 +0,0 @@
1
- {"version":3,"file":"captcha.d.ts","sourceRoot":"","sources":["../src/captcha.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAyExD,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,iBAiB9C"}
File without changes