From c4bb24f02f913ee76e30da6d8944d8430bd99c76 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 16 Aug 2024 08:03:02 -0700 Subject: [PATCH 01/25] feat(test runner): record trace after a test-scoped fixture teardown times out (#32160) Fixes #30718, fixes #31537. --- packages/playwright/src/index.ts | 7 +++- .../playwright-test/playwright.trace.spec.ts | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 8b392afa37..130e731b5a 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -248,6 +248,11 @@ const playwrightFixtures: Fixtures = ({ }, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any], _setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => { + // This fixture has a separate zero-timeout slot to ensure that artifact collection + // happens even after some fixtures or hooks time out. + // Now that default test timeout is known, we can replace zero with an actual value. + testInfo.setTimeout(testInfo.project.timeout); + const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot); await artifactsRecorder.willStartTest(testInfo as TestInfoImpl); const csiListener: ClientInstrumentationListener = { @@ -297,7 +302,7 @@ const playwrightFixtures: Fixtures = ({ clientInstrumentation.removeListener(csiListener); await artifactsRecorder.didFinishTest(); - }, { auto: 'all-hooks-included', title: 'trace recording', box: true } as any], + }, { auto: 'all-hooks-included', title: 'trace recording', box: true, timeout: 0 } as any], _contextFactory: [async ({ browser, video, _reuseContext, _combinedContextOptions /** mitigate dep-via-auto lack of traceability */ }, use, testInfo) => { const testInfoImpl = testInfo as TestInfoImpl; diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index e4a1efe4d7..5ca08925b4 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -1225,4 +1225,44 @@ test('should not nest top level expect into unfinished api calls ', { ]); }); +test('should record trace after fixture teardown timeout', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30718' }, +}, async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test as base, expect } from '@playwright/test'; + const test = base.extend({ + fixture: async ({}, use) => { + await use('foo'); + await new Promise(() => {}); + }, + }); + // Note: it is important that "fixture" is last, so that it runs the teardown first. + test('fails', async ({ page, fixture }) => { + await page.evaluate(() => console.log('from the page')); + }); + `, + }, { trace: 'on', timeout: '3000' }, { DEBUG: 'pw:test' }); + expect(result.exitCode).toBe(1); + expect(result.failed).toBe(1); + const tracePath = test.info().outputPath('test-results', 'a-fails', 'trace.zip'); + const trace = await parseTrace(tracePath); + expect(trace.actionTree).toEqual([ + 'Before Hooks', + ' fixture: browser', + ' browserType.launch', + ' fixture: context', + ' browser.newContext', + ' fixture: page', + ' browserContext.newPage', + ' fixture: fixture', + 'page.evaluate', + 'After Hooks', + ' fixture: fixture', + 'Worker Cleanup', + ' fixture: browser', + ]); + // Check console events to make sure that library trace is recorded. + expect(trace.events).toContainEqual(expect.objectContaining({ type: 'console', text: 'from the page' })); +}); From 3e6bba0b7909326c3f61b682d77b2746b776fdcf Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 16 Aug 2024 17:12:45 +0200 Subject: [PATCH 02/25] fix(only changed): make only-changed work together with list mode (#32196) Closes https://github.com/microsoft/playwright/issues/32161 Turns out we were wrong in https://github.com/microsoft/playwright/pull/31727#discussion_r1685793870! Adds support for `--only-changed` in combination with `--list` by removing our code to prevent that. --- packages/playwright/src/runner/tasks.ts | 12 +++++------ tests/playwright-test/only-changed.spec.ts | 25 +++++++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts index 4220291a08..73eb31f1e7 100644 --- a/packages/playwright/src/runner/tasks.ts +++ b/packages/playwright/src/runner/tasks.ts @@ -63,7 +63,7 @@ export class TestRun { export function createTaskRunner(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner { const taskRunner = TaskRunner.create(reporters, config.config.globalTimeout); addGlobalSetupTasks(taskRunner, config); - taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: true })); + taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true })); addRunTasks(taskRunner, config); return taskRunner; } @@ -76,14 +76,14 @@ export function createTaskRunnerForWatchSetup(config: FullConfigInternal, report export function createTaskRunnerForWatch(config: FullConfigInternal, reporters: ReporterV2[], additionalFileMatcher?: Matcher): TaskRunner { const taskRunner = TaskRunner.create(reporters); - taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true, additionalFileMatcher })); + taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true, additionalFileMatcher })); addRunTasks(taskRunner, config); return taskRunner; } export function createTaskRunnerForTestServer(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner { const taskRunner = TaskRunner.create(reporters); - taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true })); + taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true })); addRunTasks(taskRunner, config); return taskRunner; } @@ -108,7 +108,7 @@ function addRunTasks(taskRunner: TaskRunner, config: FullConfigInternal export function createTaskRunnerForList(config: FullConfigInternal, reporters: ReporterV2[], mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner { const taskRunner = TaskRunner.create(reporters, config.config.globalTimeout); - taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false, filterOnlyChanged: false })); + taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false })); taskRunner.addTask('report begin', createReportBeginTask()); return taskRunner; } @@ -222,14 +222,14 @@ function createListFilesTask(): Task { }; } -function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, filterOnlyChanged: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, additionalFileMatcher?: Matcher }): Task { +function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, additionalFileMatcher?: Matcher }): Task { return { setup: async (reporter, testRun, errors, softErrors) => { await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter, options.additionalFileMatcher); await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors); let cliOnlyChangedMatcher: Matcher | undefined = undefined; - if (testRun.config.cliOnlyChanged && options.filterOnlyChanged) { + if (testRun.config.cliOnlyChanged) { for (const plugin of testRun.config.plugins) await plugin.instance?.populateDependencies?.(); const changedFiles = await detectChangedTestFiles(testRun.config.cliOnlyChanged, testRun.config.configDir); diff --git a/tests/playwright-test/only-changed.spec.ts b/tests/playwright-test/only-changed.spec.ts index 355bcf977d..901ec7511c 100644 --- a/tests/playwright-test/only-changed.spec.ts +++ b/tests/playwright-test/only-changed.spec.ts @@ -413,4 +413,27 @@ test('should run project dependencies of changed tests', { expect(result.passed).toBe(1); expect(result.output).toContain('setup test is executed'); -}); \ No newline at end of file +}); + +test('should work with list mode', async ({ runInlineTest, git, writeFiles }) => { + await writeFiles({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('fails', () => { expect(1).toBe(2); }); + `, + }); + + git(`add .`); + git(`commit -m init`); + + const result = await runInlineTest({ + 'b.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('fails', () => { expect(1).toBe(2); }); + ` + }, { 'only-changed': true, 'list': true }); + + expect(result.exitCode).toBe(0); + expect(result.output).toContain('b.spec.ts'); + expect(result.output).not.toContain('a.spec.ts'); +}); From 743565ee3e1ef47e06abd60100ec0552ba51f077 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 16 Aug 2024 20:21:05 +0200 Subject: [PATCH 03/25] chore: generate self-signed certificates for socks proxy (#32192) --- .../playwright-core/bin/socks-certs/README.md | 11 -- .../playwright-core/bin/socks-certs/cert.pem | 19 -- .../playwright-core/bin/socks-certs/key.pem | 28 --- .../socksClientCertificatesInterceptor.ts | 10 +- packages/playwright-core/src/utils/crypto.ts | 168 ++++++++++++++++++ 5 files changed, 171 insertions(+), 65 deletions(-) delete mode 100644 packages/playwright-core/bin/socks-certs/README.md delete mode 100644 packages/playwright-core/bin/socks-certs/cert.pem delete mode 100644 packages/playwright-core/bin/socks-certs/key.pem diff --git a/packages/playwright-core/bin/socks-certs/README.md b/packages/playwright-core/bin/socks-certs/README.md deleted file mode 100644 index 4950ef1f3c..0000000000 --- a/packages/playwright-core/bin/socks-certs/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Certfificates for Socks Proxy - -These certificates are used when client certificates are used with -Playwright. Playwright then creates a Socks proxy, which sits between -the browser and the actual target server. The Socks proxy uses this certificiate -to talk to the browser and establishes its own secure TLS connection to the server. -The certificates are generated via: - -```bash -openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -keyout key.pem -out cert.pem -subj "/CN=localhost" -``` diff --git a/packages/playwright-core/bin/socks-certs/cert.pem b/packages/playwright-core/bin/socks-certs/cert.pem deleted file mode 100644 index cce2f57bd5..0000000000 --- a/packages/playwright-core/bin/socks-certs/cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDCTCCAfGgAwIBAgIUTcrzEueVL/OuLHr4LBIPWeS4UL0wDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDcwNDA4NDAzNFoXDTM0MDcw -MjA4NDAzNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEApof+SZVN4UGma4xJDVHhMSpmEJoCdMPr+HFadJJK/brF -BNOhA1C5wNk8oD/XYo7enAHQH/EsBnq4MMxv79rXTGnIdXMF+43GdMDh5kh81FQy -Esw8Vt4eif9eZkjUxI2GHhR2ovJewmQa7E+SeUB2RzJTqz8QPLhd74JFfgaci+S2 -8L37ScVjcw55T1PcNflzB4vwsQHBT3yND0MLDhm+8MLzmTl4Mw5PgIOaBl5Jh8Tr -wQF4eeeB3FPJoMQhTP8aGBjW1mo+NmSSRAPIAZyhmCAnDeC33yRjAaiHjaL5Pr9f -wt5zoF5+U1xWhGXWzGOE6p/VTj62F9a2fOXNHclYJQIDAQABo1MwUTAdBgNVHQ4E -FgQU9BoVzGtb5x70KqGO/89N1hyqi5kwHwYDVR0jBBgwFoAU9BoVzGtb5x70KqGO -/89N1hyqi5kwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYcbI -wvcfx2p8z0RNN3EA+epKX1SagZyJX4ORIO8kln1sDU+ceHde3n3xnp1dg6HG2qh1 -a7CZub/fNUaP9R8+6iiV0wPT7Ybkb2NIJcH1yq+/bfSS5OC5DO0yv9SUADdBoDwa -zOuBAqdcYW1BHYcbAzsQnniRcejHu06ioaS6SwwJ8150rQnLT4Lh9LAl40W6v4nZ -NdTGQETTrbjcgH1ER4IhWTKtVyPOxGF9A/OOawMEdfS8BhUO7YRS4QNFFaQMrJAb -MDhDtjSyDogLr8P43xjjWvQWG9a7zTF0kKEsdJ0cEG5HATpg8bPHmrouxbs2HGeH -kJXzMykrsYyXsInN3w== ------END CERTIFICATE----- diff --git a/packages/playwright-core/bin/socks-certs/key.pem b/packages/playwright-core/bin/socks-certs/key.pem deleted file mode 100644 index 75f8e3bccc..0000000000 --- a/packages/playwright-core/bin/socks-certs/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCmh/5JlU3hQaZr -jEkNUeExKmYQmgJ0w+v4cVp0kkr9usUE06EDULnA2TygP9dijt6cAdAf8SwGergw -zG/v2tdMach1cwX7jcZ0wOHmSHzUVDISzDxW3h6J/15mSNTEjYYeFHai8l7CZBrs -T5J5QHZHMlOrPxA8uF3vgkV+BpyL5LbwvftJxWNzDnlPU9w1+XMHi/CxAcFPfI0P -QwsOGb7wwvOZOXgzDk+Ag5oGXkmHxOvBAXh554HcU8mgxCFM/xoYGNbWaj42ZJJE -A8gBnKGYICcN4LffJGMBqIeNovk+v1/C3nOgXn5TXFaEZdbMY4Tqn9VOPrYX1rZ8 -5c0dyVglAgMBAAECggEAB6zX4vNPKhUZAvbtvP/rlZUDLDu05kXLX+F1jk7ZxvTv -NKg+UQVM8l7wxN/8YM3944nP2lEGuuu4BoO9mvvmlV6Avy0EdxITNflX0AHCQxT4 -U9Z253gIR0ruQl+T8tUk+8jsqNjr1iC//ukx8oWujdx7b7aR3IKQzcOeyU6rs2TN -lyrVVsEaFVi9+wCw0xyiCmPlobrn+egdigw7Zhp2BRinC6W9eMxuPS2hlhQUhBm/ -eiD96YWp0RAv/L5qO93reoXIAzrrLdcUgPEnnq1zN7y2xihU2+B2sTph1m/A26+J -yPcXd7vQrXlRXQU6PaCa+0oJULlpiAzy3HPbnr4BkQKBgQDdmekTX8dQqiEZPX1C -017QRFbx0/x/TDFDSeJbDeauMzzCaGqCO2WVmYmTvFtby2G4/6BYowVtJVHm4uJl -XsYk8dWIQGLPIj1Cw7ZieJvb2EVRxgnY2oMaOTOazHzPHFzZV718zwEeZrryT82J -881E8wgM8V3DjkS4ye3TbwvimQKBgQDAYa/IdnpAg5z1TREi9Tt8fnoGpmSscAak -USgeXVsvoNzXXkE94MiiCOOrX1r68TWYDAzq6MKGDewkWOfLwXWR6D5C2LyE1q9P -1pxstgs/nC3ZUTz0yEH47ahSmhywhGlvXXOQEXUSLiVTOdeMCubMqwQW80F1868n -aBHcj5/lbQKBgQDIojjsWaNT3TTqbUmj30vQtI8jlBLgDlPr4FEYr5VT0wAH5BHK -p4xpzgFJyRfOHG312TuMBM087LUinfjsXsp3WJ1EJ0dO0mk0sY3HyfsTKNRaHTt9 -Ixnf/DpExS+bNMq73Tyqa6FPrSNFkAtAA4SuEHwRe9aw33ZI+EpjS/8uwQKBgQCi -9NwqSLlLVnColEw0uVdXH+cLJPzX19i4bQo3lkp8MJ2ATJWk7XflUPRQoGf3ckQ8 -c9CpVtoXJUnmi+xkeo21Nu0uQFqHhzZewWIk75rdmdR4ZUjl649+ZQkUVviASNjq -fVU7Lp5k9POm6LL9K+rOaPoA2rKTUAQItC2VD4+YjQKBgB6kgvgN6Mz/u0RE3kkV -2GOoP5sso71Hxwh7o6JEzUMhR+e/T/LLcBwEjLYcf1FYRySHsXLn2Ar/Uw1J7pAZ -ud54/at+7mTDliaT8Ar7S9vcso7ZfmuDX9qB9+c77idPskVBPo2tjJbwvFcB6sww -5Elcfmj6tEP4YLJ6Kv3qTPhT ------END PRIVATE KEY----- diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 4e371df201..5510303882 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -15,14 +15,12 @@ */ import net from 'net'; -import path from 'path'; import http2 from 'http2'; import type https from 'https'; -import fs from 'fs'; import tls from 'tls'; import stream from 'stream'; import { createSocket, createTLSSocket } from '../utils/happy-eyeballs'; -import { escapeHTML, ManualPromise, rewriteErrorMessage } from '../utils'; +import { escapeHTML, generateSelfSignedCertificate, ManualPromise, rewriteErrorMessage } from '../utils'; import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy'; import { SocksProxy } from '../common/socksProxy'; import type * as channels from '@protocol/channels'; @@ -32,10 +30,8 @@ let dummyServerTlsOptions: tls.TlsOptions | undefined = undefined; function loadDummyServerCertsIfNeeded() { if (dummyServerTlsOptions) return; - dummyServerTlsOptions = { - key: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/key.pem')), - cert: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/cert.pem')), - }; + const { cert, key } = generateSelfSignedCertificate(); + dummyServerTlsOptions = { key, cert }; } class ALPNCache { diff --git a/packages/playwright-core/src/utils/crypto.ts b/packages/playwright-core/src/utils/crypto.ts index f3e47f6993..5da56d4e9b 100644 --- a/packages/playwright-core/src/utils/crypto.ts +++ b/packages/playwright-core/src/utils/crypto.ts @@ -15,6 +15,7 @@ */ import crypto from 'crypto'; +import { assert } from './debug'; export function createGuid(): string { return crypto.randomBytes(16).toString('hex'); @@ -25,3 +26,170 @@ export function calculateSha1(buffer: Buffer | string): string { hash.update(buffer); return hash.digest('hex'); } + +// Variable-length quantity encoding aka. base-128 encoding +function encodeBase128(value: number): Buffer { + const bytes = []; + do { + let byte = value & 0x7f; + value >>>= 7; + if (bytes.length > 0) byte |= 0x80; + bytes.push(byte); + } while (value > 0); + return Buffer.from(bytes.reverse()); +}; + +// ASN1/DER Speficiation: https://www.itu.int/rec/T-REC-X.680-X.693-202102-I/en +class DER { + static encodeSequence(data: Buffer[]): Buffer { + return this._encode(0x30, Buffer.concat(data)); + } + static encodeInteger(data: number): Buffer { + assert(data >= -128 && data <= 127); + return this._encode(0x02, Buffer.from([data])); + } + static encodeObjectIdentifier(oid: string): Buffer { + const parts = oid.split('.').map((v) => Number(v)); + // Encode the second part, which could be large, using base-128 encoding if necessary + const output = [encodeBase128(40 * parts[0] + parts[1])]; + + for (let i = 2; i < parts.length; i++) { + output.push(encodeBase128(parts[i])); + } + + return this._encode(0x06, Buffer.concat(output)); + } + static encodeNull(): Buffer { + return Buffer.from([0x05, 0x00]); + } + static encodeSet(data: Buffer[]): Buffer { + assert(data.length === 1, 'Only one item in the set is supported. We\'d need to sort the data to support more.'); + // We expect the data to be already sorted. + return this._encode(0x31, Buffer.concat(data)); + } + static encodeExplicitContextDependent(tag: number, data: Buffer): Buffer { + return this._encode(0xa0 + tag, data); + } + static encodePrintableString(data: string): Buffer { + return this._encode(0x13, Buffer.from(data)); + } + static encodeBitString(data: Buffer): Buffer { + // The first byte of the content is the number of unused bits at the end + const unusedBits = 0; // Assuming all bits are used + const content = Buffer.concat([Buffer.from([unusedBits]), data]); + return this._encode(0x03, content); + } + static encodeDate(date: Date): Buffer { + const year = date.getUTCFullYear(); + const isGeneralizedTime = year >= 2050; + const parts = [ + isGeneralizedTime ? year.toString() : year.toString().slice(-2), + (date.getUTCMonth() + 1).toString().padStart(2, '0'), + date.getUTCDate().toString().padStart(2, '0'), + date.getUTCHours().toString().padStart(2, '0'), + date.getUTCMinutes().toString().padStart(2, '0'), + date.getUTCSeconds().toString().padStart(2, '0') + ]; + const encodedDate = parts.join('') + 'Z'; + const tag = isGeneralizedTime ? 0x18 : 0x17; // 0x18 for GeneralizedTime, 0x17 for UTCTime + return this._encode(tag, Buffer.from(encodedDate)); + } + private static _encode(tag: number, data: Buffer): Buffer { + const lengthBytes = this._encodeLength(data.length); + return Buffer.concat([Buffer.from([tag]), lengthBytes, data]); + } + private static _encodeLength(length: number): Buffer { + if (length < 128) { + return Buffer.from([length]); + } else { + const lengthBytes = []; + while (length > 0) { + lengthBytes.unshift(length & 0xFF); + length >>= 8; + } + return Buffer.from([0x80 | lengthBytes.length, ...lengthBytes]); + } + } +} + +// X.509 Specification: https://datatracker.ietf.org/doc/html/rfc2459#section-4.1 +export function generateSelfSignedCertificate() { + const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); + const publicKeyDer = publicKey.export({ type: 'pkcs1', format: 'der' }); + + const oneYearInMilliseconds = 365 * 24 * 60 * 60 * 1_000; + const notBefore = new Date(new Date().getTime() - oneYearInMilliseconds); + const notAfter = new Date(new Date().getTime() + oneYearInMilliseconds); + + // List of fields / structure: https://datatracker.ietf.org/doc/html/rfc2459#section-4.1 + const tbsCertificate = DER.encodeSequence([ + DER.encodeExplicitContextDependent(0, DER.encodeInteger(1)), // version + DER.encodeInteger(1), // serialNumber + DER.encodeSequence([ + DER.encodeObjectIdentifier('1.2.840.113549.1.1.11'), // sha256WithRSAEncryption PKCS #1 + DER.encodeNull() + ]), // signature + DER.encodeSequence([ + DER.encodeSet([ + DER.encodeSequence([ + DER.encodeObjectIdentifier('2.5.4.3'), // commonName X.520 DN component + DER.encodePrintableString('localhost') + ]), + ]), + DER.encodeSet([ + DER.encodeSequence([ + DER.encodeObjectIdentifier('2.5.4.10'), // organizationName X.520 DN component + DER.encodePrintableString('Playwright Client Certificate Support') + ]) + ]) + ]), // issuer + DER.encodeSequence([ + DER.encodeDate(notBefore), // notBefore + DER.encodeDate(notAfter), // notAfter + ]), // validity + DER.encodeSequence([ + DER.encodeSet([ + DER.encodeSequence([ + DER.encodeObjectIdentifier('2.5.4.3'), // commonName X.520 DN component + DER.encodePrintableString('localhost') + ]), + ]), + DER.encodeSet([ + DER.encodeSequence([ + DER.encodeObjectIdentifier('2.5.4.10'), // organizationName X.520 DN component + DER.encodePrintableString('Playwright Client Certificate Support') + ]) + ]) + ]), // subject + DER.encodeSequence([ + DER.encodeSequence([ + DER.encodeObjectIdentifier('1.2.840.113549.1.1.1'), // rsaEncryption PKCS #1 + DER.encodeNull() + ]), + DER.encodeBitString(publicKeyDer) + ]), // SubjectPublicKeyInfo + ]); + + const signature = crypto.sign('sha256', tbsCertificate, privateKey); + + const certificate = DER.encodeSequence([ + tbsCertificate, + DER.encodeSequence([ + DER.encodeObjectIdentifier('1.2.840.113549.1.1.11'), // sha256WithRSAEncryption PKCS #1 + DER.encodeNull() + ]), + DER.encodeBitString(signature) + ]); + + const certPem = [ + '-----BEGIN CERTIFICATE-----', + // Split the base64 string into lines of 64 characters + certificate.toString('base64').match(/.{1,64}/g)!.join('\n'), + '-----END CERTIFICATE-----' + ].join('\n'); + + return { + cert: certPem, + key: privateKey.export({ type: 'pkcs1', format: 'pem' }), + }; +} From 570e05699e3b70b942e902bd93cfc783b73930a4 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Sat, 17 Aug 2024 01:56:18 -0700 Subject: [PATCH 04/25] feat(chromium-tip-of-tree): roll to r1250 (#32202) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 2f987ac082..d0b427f41b 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1249", + "revision": "1250", "installByDefault": false, - "browserVersion": "129.0.6654.0" + "browserVersion": "129.0.6658.0" }, { "name": "firefox", From faf4853259d9efdc9802ed79fe36490740dd8424 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 19 Aug 2024 09:02:14 +0200 Subject: [PATCH 05/25] chore: validate client-certificates on context creation (#32168) --- packages/playwright-core/src/server/fetch.ts | 6 +- .../socksClientCertificatesInterceptor.ts | 70 ++++++++++++------- tests/library/client-certificates.spec.ts | 14 ++-- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 6367e8875e..a4d9cd9ee2 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -40,7 +40,7 @@ import { Tracing } from './trace/recorder/tracing'; import type * as types from './types'; import type { HeadersArray, ProxySettings } from './types'; import { kMaxCookieExpiresDateInSeconds } from './network'; -import { clientCertificatesToTLSOptions, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor'; +import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor'; type FetchRequestOptions = { userAgent: string; @@ -195,7 +195,7 @@ export abstract class APIRequestContext extends SdkObject { maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects, timeout, deadline, - ...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, requestUrl.origin), + ...getMatchingTLSOptionsForOrigin(this._defaultOptions().clientCertificates, requestUrl.origin), __testHookLookup: (params as any).__testHookLookup, }; // rejectUnauthorized = undefined is treated as true in Node.js 12. @@ -365,7 +365,7 @@ export abstract class APIRequestContext extends SdkObject { maxRedirects: options.maxRedirects - 1, timeout: options.timeout, deadline: options.deadline, - ...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, url.origin), + ...getMatchingTLSOptionsForOrigin(this._defaultOptions().clientCertificates, url.origin), __testHookLookup: options.__testHookLookup, }; // rejectUnauthorized = undefined is treated as true in node 12. diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 5510303882..32a7b1cebd 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -157,7 +157,6 @@ class SocksProxyConnection { let targetTLS: tls.TLSSocket | undefined = undefined; const handleError = (error: Error) => { - error = rewriteOpenSSLErrorIfNeeded(error); debugLogger.log('client-certificates', `error when connecting to target: ${error.message.replaceAll('\n', ' ')}`); const responseBody = escapeHTML('Playwright client-certificate error: ' + error.message) .replaceAll('\n', '
'); @@ -198,14 +197,6 @@ class SocksProxyConnection { } }; - let secureContext: tls.SecureContext; - try { - secureContext = tls.createSecureContext(clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, new URL(`https://${this.host}:${this.port}`).origin)); - } catch (error) { - handleError(error); - return; - } - if (this._closed) { internalTLS.destroy(); return; @@ -217,7 +208,7 @@ class SocksProxyConnection { rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors, ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'], servername: !net.isIP(this.host) ? this.host : undefined, - secureContext, + secureContext: this.socksProxy.secureContextMap.get(new URL(`https://${this.host}:${this.port}`).origin), }); targetTLS.once('secureConnect', () => { @@ -236,7 +227,7 @@ export class ClientCertificatesProxy { _socksProxy: SocksProxy; private _connections: Map = new Map(); ignoreHTTPSErrors: boolean | undefined; - clientCertificates: channels.BrowserNewContextOptions['clientCertificates']; + secureContextMap: Map = new Map(); alpnCache: ALPNCache; constructor( @@ -244,7 +235,7 @@ export class ClientCertificatesProxy { ) { this.alpnCache = new ALPNCache(); this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors; - this.clientCertificates = contextOptions.clientCertificates; + this._initSecureContexts(contextOptions.clientCertificates); this._socksProxy = new SocksProxy(); this._socksProxy.setPattern('*'); this._socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => { @@ -266,6 +257,27 @@ export class ClientCertificatesProxy { loadDummyServerCertsIfNeeded(); } + _initSecureContexts(clientCertificates: channels.BrowserNewContextOptions['clientCertificates']) { + // Step 1. Group certificates by origin. + const origin2certs = new Map(); + for (const cert of clientCertificates || []) { + const origin = normalizeOrigin(cert.origin); + const certs = origin2certs.get(origin) || []; + certs.push(cert); + origin2certs.set(origin, certs); + } + + // Step 2. Create secure contexts for each origin. + for (const [origin, certs] of origin2certs) { + try { + this.secureContextMap.set(origin, tls.createSecureContext(convertClientCertificatesToTLSOptions(certs))); + } catch (error) { + error = rewriteOpenSSLErrorIfNeeded(error); + throw rewriteErrorMessage(error, `Failed to load client certificate: ${error.message}`); + } + } + } + public async listen(): Promise { const port = await this._socksProxy.listen(0, '127.0.0.1'); return `socks5://127.0.0.1:${port}`; @@ -276,25 +288,25 @@ export class ClientCertificatesProxy { } } -export function clientCertificatesToTLSOptions( - clientCertificates: channels.BrowserNewContextOptions['clientCertificates'], - origin: string +function normalizeOrigin(origin: string): string { + try { + return new URL(origin).origin; + } catch (error) { + return origin; + } +} + +function convertClientCertificatesToTLSOptions( + clientCertificates: channels.BrowserNewContextOptions['clientCertificates'] ): Pick | undefined { - const matchingCerts = clientCertificates?.filter(c => { - try { - return new URL(c.origin).origin === origin; - } catch (error) { - return c.origin === origin; - } - }); - if (!matchingCerts || !matchingCerts.length) + if (!clientCertificates || !clientCertificates.length) return; const tlsOptions = { pfx: [] as { buf: Buffer, passphrase?: string }[], key: [] as { pem: Buffer, passphrase?: string }[], cert: [] as Buffer[], }; - for (const cert of matchingCerts) { + for (const cert of clientCertificates) { if (cert.cert) tlsOptions.cert.push(cert.cert); if (cert.key) @@ -305,6 +317,16 @@ export function clientCertificatesToTLSOptions( return tlsOptions; } +export function getMatchingTLSOptionsForOrigin( + clientCertificates: channels.BrowserNewContextOptions['clientCertificates'], + origin: string +): Pick | undefined { + const matchingCerts = clientCertificates?.filter(c => + normalizeOrigin(c.origin) === origin + ); + return convertClientCertificatesToTLSOptions(matchingCerts); +} + function rewriteToLocalhostIfNeeded(host: string): string { return host === 'local.playwright' ? 'localhost' : host; } diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index 11508e1081..fa7eb8bbbc 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -362,32 +362,26 @@ test.describe('browser', () => { test('should fail with matching certificates in legacy pfx format', async ({ browser, startCCServer, asset, browserName }) => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); - const page = await browser.newPage({ + await expect(browser.newPage({ ignoreHTTPSErrors: true, clientCertificates: [{ origin: new URL(serverURL).origin, pfxPath: asset('client-certificates/client/trusted/cert-legacy.pfx'), passphrase: 'secure' }], - }); - await page.goto(serverURL); - await expect(page.getByText('Unsupported TLS certificate.')).toBeVisible(); - await page.close(); + })).rejects.toThrow('Unsupported TLS certificate'); }); test('should throw a http error if the pfx passphrase is incorect', async ({ browser, startCCServer, asset, browserName }) => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); - const page = await browser.newPage({ + await expect(browser.newPage({ ignoreHTTPSErrors: true, clientCertificates: [{ origin: new URL(serverURL).origin, pfxPath: asset('client-certificates/client/trusted/cert.pfx'), passphrase: 'this-password-is-incorrect' }], - }); - await page.goto(serverURL); - await expect(page.getByText('Playwright client-certificate error: mac verify failure')).toBeVisible(); - await page.close(); + })).rejects.toThrow('Failed to load client certificate: mac verify failure'); }); test('should pass with matching certificates on context APIRequestContext instance', async ({ browser, startCCServer, asset, browserName }) => { From 74f5ce54896eebec4050470874cc9a9ec4a50965 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 19 Aug 2024 09:11:20 +0200 Subject: [PATCH 06/25] docs: store parent type reference in documentation.js (#32215) --- utils/doclint/documentation.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/utils/doclint/documentation.js b/utils/doclint/documentation.js index 1c63421524..77b20ca769 100644 --- a/utils/doclint/documentation.js +++ b/utils/doclint/documentation.js @@ -353,6 +353,8 @@ class Member { this.clazz = null; /** @type {Member=} */ this.enclosingMethod = undefined; + /** @type {Member=} */ + this.parent = undefined; this.async = false; this.alias = name; this.overloadIndex = 0; @@ -372,10 +374,11 @@ class Member { this.args = new Map(); if (this.kind === 'method') this.enclosingMethod = this; - const indexType = type => { - type.deepProperties().forEach(p => { + const indexArg = (/** @type {Member} */ arg) => { + arg.type?.deepProperties().forEach(p => { p.enclosingMethod = this; - indexType(p.type); + p.parent = arg; + indexArg(p); }); } for (const arg of this.argsArray) { @@ -385,7 +388,7 @@ class Member { // @ts-ignore arg.type.properties.sort((p1, p2) => p1.name.localeCompare(p2.name)); } - indexType(arg.type); + indexArg(arg); } } From 010778f6c5543d112a65d18fff34556c30bf5776 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 19 Aug 2024 09:24:32 +0200 Subject: [PATCH 07/25] feat(client-certificates): allow passing certificates from memory (#32210) --- docs/src/api/params.md | 5 +- .../src/client/browserContext.ts | 24 +++-- packages/playwright-core/src/client/types.ts | 3 + packages/playwright-core/types/types.d.ts | 92 +++++++++++++++---- packages/playwright/types/test.d.ts | 8 +- tests/library/client-certificates.spec.ts | 30 ++++++ 6 files changed, 132 insertions(+), 30 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index f4adc23a3c..5aa9c6a5ab 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -531,15 +531,18 @@ Does not enforce fixed viewport, allows resizing window in the headed mode. - `clientCertificates` <[Array]<[Object]>> - `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. - `certPath` ?<[path]> Path to the file with the certificate in PEM format. + - `cert` ?<[Buffer]> Direct value of the certificate in PEM format. - `keyPath` ?<[path]> Path to the file with the private key in PEM format. + - `key` ?<[Buffer]> Direct value of the private key in PEM format. - `pfxPath` ?<[path]> Path to the PFX or PKCS12 encoded private key and certificate chain. + - `pfx` ?<[Buffer]> Direct value of the PFX or PKCS12 encoded private key and certificate chain. - `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX). TLS Client Authentication allows the server to request a client certificate and verify it. **Details** -An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. +An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. :::note Using Client Certificates in combination with Proxy Servers is not supported. diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 5991fcafd9..c4f7827840 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -552,13 +552,19 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) { export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise { if (!certs) return undefined; - return await Promise.all(certs.map(async cert => { - return { - origin: cert.origin, - cert: cert.certPath ? await fs.promises.readFile(cert.certPath) : undefined, - key: cert.keyPath ? await fs.promises.readFile(cert.keyPath) : undefined, - pfx: cert.pfxPath ? await fs.promises.readFile(cert.pfxPath) : undefined, - passphrase: cert.passphrase, - }; - })); + + const bufferizeContent = async (value?: Buffer, path?: string): Promise => { + if (value) + return value; + if (path) + return await fs.promises.readFile(path); + }; + + return await Promise.all(certs.map(async cert => ({ + origin: cert.origin, + cert: await bufferizeContent(cert.cert, cert.certPath), + key: await bufferizeContent(cert.key, cert.keyPath), + pfx: await bufferizeContent(cert.pfx, cert.pfxPath), + passphrase: cert.passphrase, + }))); } diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 8101257280..37d374e3ec 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -49,8 +49,11 @@ export const kLifecycleEvents: Set = new Set(['load', 'domconten export type ClientCertificate = { origin: string; + cert?: Buffer; certPath?: string; + key?: Buffer; keyPath?: string; + pfx?: Buffer; pfxPath?: string; passphrase?: string; }; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index bf4e35a9ca..a4bf9fa812 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -9138,10 +9138,10 @@ export interface Browser { * * **Details** * - * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a - * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided + * with an exact match to the request origin that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -9159,16 +9159,31 @@ export interface Browser { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ @@ -13850,10 +13865,10 @@ export interface BrowserType { * * **Details** * - * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a - * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided + * with an exact match to the request origin that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -13871,16 +13886,31 @@ export interface BrowserType { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ @@ -16259,10 +16289,10 @@ export interface APIRequest { * * **Details** * - * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a - * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided + * with an exact match to the request origin that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -16280,16 +16310,31 @@ export interface APIRequest { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ @@ -20600,10 +20645,10 @@ export interface BrowserContextOptions { * * **Details** * - * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a - * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided + * with an exact match to the request origin that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -20621,16 +20666,31 @@ export interface BrowserContextOptions { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 28bc7798b5..0131cb16c9 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5206,10 +5206,10 @@ export interface PlaywrightTestOptions { * * **Details** * - * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a - * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided + * with an exact match to the request origin that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index fa7eb8bbbc..49e0dc0dcc 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -303,6 +303,21 @@ test.describe('browser', () => { await page.close(); }); + test('should pass with matching certificates when passing as content', async ({ browser, startCCServer, asset, browserName }) => { + const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); + const page = await browser.newPage({ + ignoreHTTPSErrors: true, + clientCertificates: [{ + origin: new URL(serverURL).origin, + cert: await fs.promises.readFile(asset('client-certificates/client/trusted/cert.pem')), + key: await fs.promises.readFile(asset('client-certificates/client/trusted/key.pem')), + }], + }); + await page.goto(serverURL); + await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!'); + await page.close(); + }); + test('should not hang on tls errors during TLS 1.2 handshake', async ({ browser, asset, platform, browserName }) => { for (const tlsVersion of ['TLSv1.3', 'TLSv1.2'] as const) { await test.step(`TLS version: ${tlsVersion}`, async () => { @@ -360,6 +375,21 @@ test.describe('browser', () => { await page.close(); }); + test('should pass with matching certificates in pfx format when passing as content', async ({ browser, startCCServer, asset, browserName }) => { + const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); + const page = await browser.newPage({ + ignoreHTTPSErrors: true, + clientCertificates: [{ + origin: new URL(serverURL).origin, + pfx: await fs.promises.readFile(asset('client-certificates/client/trusted/cert.pfx')), + passphrase: 'secure' + }], + }); + await page.goto(serverURL); + await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!'); + await page.close(); + }); + test('should fail with matching certificates in legacy pfx format', async ({ browser, startCCServer, asset, browserName }) => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); await expect(browser.newPage({ From c87ca052d1281b94f9c6a269b178cc0277f36027 Mon Sep 17 00:00:00 2001 From: Sander Date: Mon, 19 Aug 2024 14:50:25 +0200 Subject: [PATCH 08/25] fix(ct): vue jsx component.update type (#32213) partial fix for: https://github.com/microsoft/playwright/issues/31927#issuecomment-2267065378 The options object wasn't treated as partial, unlike in other frameworks, which led to the `component.update({ props: {} })` type being selected instead the `component.update()` during jsx usage. --- packages/playwright-ct-vue/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-ct-vue/index.d.ts b/packages/playwright-ct-vue/index.d.ts index 2d182d4588..822711f4b6 100644 --- a/packages/playwright-ct-vue/index.d.ts +++ b/packages/playwright-ct-vue/index.d.ts @@ -55,7 +55,7 @@ export interface MountResultJsx extends Locator { export const test: TestType<{ mount( component: JSX.Element, - options: MountOptionsJsx + options?: MountOptionsJsx ): Promise; mount( component: Component, From 18694f684341580d13eb3942ea7f26c60ed1f4ba Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:29:23 -0700 Subject: [PATCH 09/25] feat(webkit): roll to r2062 (#32147) --- packages/playwright-core/browsers.json | 2 +- tests/library/browsercontext-viewport-mobile.spec.ts | 7 +++++-- tests/library/browsercontext-viewport.spec.ts | 3 ++- tests/library/modernizr.spec.ts | 6 ++++-- tests/page/page-goto.spec.ts | 3 ++- tests/page/page-screenshot.spec.ts | 1 + 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index d0b427f41b..dd9fd60588 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -27,7 +27,7 @@ }, { "name": "webkit", - "revision": "2061", + "revision": "2062", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", diff --git a/tests/library/browsercontext-viewport-mobile.spec.ts b/tests/library/browsercontext-viewport-mobile.spec.ts index 8160d5601e..1f897f60fa 100644 --- a/tests/library/browsercontext-viewport-mobile.spec.ts +++ b/tests/library/browsercontext-viewport-mobile.spec.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import os from 'os'; import { browserTest as it, expect } from '../config/browserTest'; it.describe('mobile viewport', () => { @@ -54,7 +55,8 @@ it.describe('mobile viewport', () => { } }); - it('should be detectable by Modernizr', async ({ playwright, browser, server }) => { + it('should be detectable by Modernizr', async ({ playwright, browser, server, browserName, platform }) => { + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'detect-touch.html uses Modernizr which uses WebGL. WebGL is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277'); const iPhone = playwright.devices['iPhone 6']; const context = await browser.newContext({ ...iPhone }); const page = await context.newPage(); @@ -63,7 +65,8 @@ it.describe('mobile viewport', () => { await context.close(); }); - it('should detect touch when applying viewport with touches', async ({ browser, server }) => { + it('should detect touch when applying viewport with touches', async ({ browser, server, browserName, platform }) => { + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'Modernizr uses WebGL. WebGL is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277'); const context = await browser.newContext({ viewport: { width: 800, height: 600 }, hasTouch: true }); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); diff --git a/tests/library/browsercontext-viewport.spec.ts b/tests/library/browsercontext-viewport.spec.ts index 360cc5815d..4ba1e2f52d 100644 --- a/tests/library/browsercontext-viewport.spec.ts +++ b/tests/library/browsercontext-viewport.spec.ts @@ -93,7 +93,8 @@ it('should emulate availWidth and availHeight', async ({ page }) => { expect(await page.evaluate(() => window.screen.availHeight)).toBe(600); }); -it('should not have touch by default', async ({ page, server }) => { +it('should not have touch by default', async ({ page, server, browserName, platform }) => { + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'detect-touch.html uses Modernizr which uses WebGL. WebGL is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277'); await page.goto(server.PREFIX + '/mobile.html'); expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); await page.goto(server.PREFIX + '/detect-touch.html'); diff --git a/tests/library/modernizr.spec.ts b/tests/library/modernizr.spec.ts index 5f0e165c65..327a3c4169 100644 --- a/tests/library/modernizr.spec.ts +++ b/tests/library/modernizr.spec.ts @@ -32,7 +32,8 @@ async function checkFeatures(name: string, context: any, server: any) { it('safari-14-1', async ({ browser, browserName, platform, server, headless, isMac }) => { it.skip(browserName !== 'webkit'); - it.skip(browserName === 'webkit' && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen.'); + it.skip(browserName === 'webkit' && platform === 'darwin', 'WebKit for macOS 10.15 is frozen.'); + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'Modernizr uses WebGL which is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277'); const context = await browser.newContext({ deviceScaleFactor: 2 }); @@ -81,7 +82,8 @@ it('safari-14-1', async ({ browser, browserName, platform, server, headless, isM it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, isMac, server, headless }) => { it.skip(browserName !== 'webkit'); - it.skip(browserName === 'webkit' && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen.'); + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen.'); + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'Modernizr uses WebGL which is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277'); const iPhone = playwright.devices['iPhone 12']; const context = await browser.newContext(iPhone); const { actual, expected } = await checkFeatures('mobile-safari-14-1', context, server); diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index a613a7e036..79107c0d21 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -181,7 +181,8 @@ it('should work with Cross-Origin-Opener-Policy after redirect', async ({ page, it('should properly cancel Cross-Origin-Opener-Policy navigation', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32107' }, -}, async ({ page, server }) => { +}, async ({ page, server, browserName, isLinux }) => { + it.fixme(browserName === 'webkit' && isLinux, 'Started failing after https://commits.webkit.org/281488@main'); server.setRoute('/empty.html', (req, res) => { res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.end(); diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index 7f374ac59b..8d0e68e12d 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -323,6 +323,7 @@ it.describe('page screenshot', () => { it('should work for webgl', async ({ page, server, browserName, platform }) => { it.fixme(browserName === 'firefox'); it.fixme(browserName === 'chromium' && platform === 'darwin' && os.arch() === 'arm64', 'SwiftShader is not available on macOS-arm64 - https://github.com/microsoft/playwright/issues/28216'); + it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'WebGL is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277'); await page.setViewportSize({ width: 640, height: 480 }); await page.goto(server.PREFIX + '/screenshots/webgl.html'); From 5271c26af12a8bd69894dde3e9ddf5cb396be9e4 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 19 Aug 2024 10:29:51 -0700 Subject: [PATCH 10/25] fix(trace viewer): do not serve resources with x-unknown content type (#32219) `x-unknown` is used as a placeholder for "no content-type" in the har. We should not send it to the browser, because it is meaningfully different from not sending `Content-Type` header. For example, Chromium refuses to interpret stylesheets served with `x-unknown` content type. Fixes https://github.com/microsoft/playwright-java/issues/1651. --- packages/trace-viewer/src/snapshotServer.ts | 4 +++- packages/trace-viewer/src/traceModel.ts | 8 +++++--- tests/library/trace-viewer.spec.ts | 12 ++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/trace-viewer/src/snapshotServer.ts b/packages/trace-viewer/src/snapshotServer.ts index c3c2f5a624..b1dd371cb3 100644 --- a/packages/trace-viewer/src/snapshotServer.ts +++ b/packages/trace-viewer/src/snapshotServer.ts @@ -85,7 +85,9 @@ export class SnapshotServer { contentType = `${contentType}; charset=utf-8`; const headers = new Headers(); - headers.set('Content-Type', contentType); + // "x-unknown" in the har means "no content type". + if (contentType !== 'x-unknown') + headers.set('Content-Type', contentType); for (const { name, value } of resource.response.headers) headers.set(name, value); headers.delete('Content-Encoding'); diff --git a/packages/trace-viewer/src/traceModel.ts b/packages/trace-viewer/src/traceModel.ts index 0fc1a73efa..1248dde967 100644 --- a/packages/trace-viewer/src/traceModel.ts +++ b/packages/trace-viewer/src/traceModel.ts @@ -114,9 +114,11 @@ export class TraceModel { async resourceForSha1(sha1: string): Promise { const blob = await this._backend.readBlob('resources/' + sha1); - if (!blob) - return; - return new Blob([blob], { type: this._resourceToContentType.get(sha1) || 'application/octet-stream' }); + const contentType = this._resourceToContentType.get(sha1); + // "x-unknown" in the har means "no content type". + if (!blob || contentType === undefined || contentType === 'x-unknown') + return blob; + return new Blob([blob], { type: contentType }); } storage(): SnapshotStorage { diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index e8471c16d6..3af586d67f 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -1439,3 +1439,15 @@ test('should show baseURL in metadata pane', { await traceViewer.showMetadataTab(); await expect(traceViewer.metadataTab).toContainText('baseURL:https://example.com'); }); + +test('should serve css without content-type', async ({ page, runAndTrace, server }) => { + server.setRoute('/one-style.css', (req, res) => { + res.writeHead(200); + res.end(`body { background: red }`); + }); + const traceViewer = await runAndTrace(async () => { + await page.goto(server.PREFIX + '/one-style.html'); + }); + const snapshotFrame = await traceViewer.snapshotFrame('page.goto'); + await expect(snapshotFrame.locator('body')).toHaveCSS('background-color', 'rgb(255, 0, 0)', { timeout: 0 }); +}); From f7e0bd30984e1875918eff4fc486d9d831bb97af Mon Sep 17 00:00:00 2001 From: Kuba Janik Date: Tue, 20 Aug 2024 08:28:02 +0200 Subject: [PATCH 11/25] feat(ui-mode): add font preview to network tab (#32209) Resolves https://github.com/microsoft/playwright/issues/32218 Currently, fonts are displayed as a raw binary file which does not give any information to the users. I replaced it with a simple font preview similar to the one found in the dev tools of web browsers. It is not a major feature but I think it is a nice addition and it might be useful to somebody. Screenshot 2024-08-17 at 18 33 46 https://github.com/user-attachments/assets/e52d9a72-fb2c-43c7-bfee-3d6d6edc6b6a --- .../src/ui/networkResourceDetails.css | 16 +++++++ .../src/ui/networkResourceDetails.tsx | 43 ++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.css b/packages/trace-viewer/src/ui/networkResourceDetails.css index 0914d065bb..a2d3f5a86f 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.css +++ b/packages/trace-viewer/src/ui/networkResourceDetails.css @@ -48,6 +48,22 @@ overflow: hidden; } +.network-font-preview { + font-family: font-preview; + font-size: 30px; + line-height: 40px; + padding: 16px; + padding-left: 6px; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} + +.network-font-preview-error { + margin-top: 8px; + text-align: center; +} + .tab-network .toolbar { min-height: 30px !important; background-color: initial !important; diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.tsx b/packages/trace-viewer/src/ui/networkResourceDetails.tsx index 3a760c999f..3eb2f8ef73 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.tsx +++ b/packages/trace-viewer/src/ui/networkResourceDetails.tsx @@ -101,12 +101,13 @@ const ResponseTab: React.FunctionComponent<{ const BodyTab: React.FunctionComponent<{ resource: ResourceSnapshot; }> = ({ resource }) => { - const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, mimeType?: string } | null>(null); + const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, mimeType?: string, font?: BinaryData } | null>(null); React.useEffect(() => { const readResources = async () => { if (resource.response.content._sha1) { const useBase64 = resource.response.content.mimeType.includes('image'); + const isFont = resource.response.content.mimeType.includes('font'); const response = await fetch(`sha1/${resource.response.content._sha1}`); if (useBase64) { const blob = await response.blob(); @@ -114,6 +115,9 @@ const BodyTab: React.FunctionComponent<{ const eventPromise = new Promise(f => reader.onload = f); reader.readAsDataURL(blob); setResponseBody({ dataUrl: (await eventPromise).target.result }); + } else if (isFont) { + const font = await response.arrayBuffer(); + setResponseBody({ font }); } else { const formattedBody = formatBody(await response.text(), resource.response.content.mimeType); setResponseBody({ text: formattedBody, mimeType: resource.response.content.mimeType }); @@ -128,11 +132,48 @@ const BodyTab: React.FunctionComponent<{ return
{!resource.response.content._sha1 &&
Response body is not available for this request.
} + {responseBody && responseBody.font && } {responseBody && responseBody.dataUrl && } {responseBody && responseBody.text && }
; }; +const FontPreview: React.FunctionComponent<{ + font: BinaryData; +}> = ({ font }) => { + const [isError, setIsError] = React.useState(false); + + React.useEffect(() => { + let fontFace: FontFace; + try { + // note: constant font family name will lead to bugs + // when displaying two font previews. + fontFace = new FontFace('font-preview', font); + if (fontFace.status === 'loaded') + document.fonts.add(fontFace); + if (fontFace.status === 'error') + setIsError(true); + } catch { + setIsError(true); + } + + return () => { + document.fonts.delete(fontFace); + }; + }, [font]); + + if (isError) + return
Could not load font preview
; + + return
+ ABCDEFGHIJKLM
+ NOPQRSTUVWXYZ
+ abcdefghijklm
+ nopqrstuvwxyz
+ 1234567890 +
; +}; + function statusClass(statusCode: number): string { if (statusCode < 300 || statusCode === 304) return 'green-circle'; From 244761a3a2479cbfdc42102afba23e811c035358 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 20 Aug 2024 09:03:02 +0200 Subject: [PATCH 12/25] chore(docs): Rework CI docs (#31988) This PR moves around some of our CI docs. It moves the GitHub actions docs from `ci-intro.md` to `ci.md`, reduces `ci-intro.md` to be an introduction, adds a mention of Sharding to the best practices, and adds a section on `--only-changed` called "Fail-Fast". Each of those changes is a separate commit, to make this a little easier to review. If we find any of those to commits to be contentious, i'll pull them out into individual PRs. While rolling this to playwright.dev, we'll also make the following changes to its sidebar: - move the `ci.md` document from the "Integrations" section to the "Playwright Test" section - make "Best Practices" the last item of the "Getting Started" section --------- Signed-off-by: Simon Knott Co-authored-by: Yury Semikhatsky --- docs/src/best-practices-js.md | 2 +- docs/src/ci-intro.md | 308 +++----------------------- docs/src/ci.md | 392 +++++++++++++++++++++++++++++++++- 3 files changed, 420 insertions(+), 282 deletions(-) diff --git a/docs/src/best-practices-js.md b/docs/src/best-practices-js.md index 7a57193699..7d4830b6cb 100644 --- a/docs/src/best-practices-js.md +++ b/docs/src/best-practices-js.md @@ -261,7 +261,7 @@ npx playwright --version Setup CI/CD and run your tests frequently. The more often you run your tests the better. Ideally you should run your tests on each commit and pull request. Playwright comes with a [GitHub actions workflow](/ci-intro.md) so that tests will run on CI for you with no setup required. Playwright can also be setup on the [CI environment](/ci.md) of your choice. -Use Linux when running your tests on CI as it is cheaper. Developers can use whatever environment when running locally but use linux on CI. +Use Linux when running your tests on CI as it is cheaper. Developers can use whatever environment when running locally but use linux on CI. Consider setting up [Sharding](./test-sharding.md) to make CI faster. ### Lint your tests diff --git a/docs/src/ci-intro.md b/docs/src/ci-intro.md index bad1e358c2..492130088a 100644 --- a/docs/src/ci-intro.md +++ b/docs/src/ci-intro.md @@ -1,19 +1,17 @@ --- id: ci-intro -title: "CI GitHub Actions" +title: "Setting up CI" --- ## Introduction * langs: js -Playwright tests can be run on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed [doc on Continuous Integration](./ci.md). - -When [installing Playwright](./intro.md) using the [VS Code extension](./getting-started-vscode.md) or with `npm init playwright@latest` you are given the option to add a [GitHub Actions](https://docs.github.com/en/actions) workflow. This creates a `playwright.yml` file inside a `.github/workflows` folder containing everything you need so that your tests run on each push and pull request into the main/master branch. +Playwright tests can be run on any CI provider. This guide covers one way of running tests on GitHub using GitHub actions. If you would like to learn more, or how to configure other CI providers, check out our detailed [doc on Continuous Integration](./ci.md). #### You will learn * langs: js -- [How to run tests on push/pull_request](/ci-intro.md#on-pushpull_request) +- [How to set up GitHub Actions](/ci-intro.md#setting-up-github-actions) - [How to view test logs](/ci-intro.md#viewing-test-logs) - [How to view the HTML report](/ci-intro.md#viewing-the-html-report) - [How to view the trace](/ci-intro.md#viewing-the-trace) @@ -25,22 +23,18 @@ When [installing Playwright](./intro.md) using the [VS Code extension](./getting Playwright tests can be ran on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration. -To add a [GitHub Actions](https://docs.github.com/en/actions) file first create `.github/workflows` folder and inside it add a `playwright.yml` file containing the example code below so that your tests will run on each push and pull request for the main/master branch. - #### You will learn * langs: python, java, csharp -- [How to run tests on push/pull_request](/ci-intro.md#on-pushpull_request) +- [How to set up GitHub Actions](/ci-intro.md#setting-up-github-actions) - [How to view test logs](/ci-intro.md#viewing-test-logs) - [How to view the trace](/ci-intro.md#viewing-the-trace) ## Setting up GitHub Actions - -### On push/pull_request * langs: js -Tests will run on push or pull request on branches main/master. The [workflow](https://docs.github.com/en/actions/using-workflows/about-workflows) will install all dependencies, install Playwright and then run the tests. It will also create the HTML report. +When [installing Playwright](./intro.md) using the [VS Code extension](./getting-started-vscode.md) or with `npm init playwright@latest` you are given the option to add a [GitHub Actions](https://docs.github.com/en/actions) workflow. This creates a `playwright.yml` file inside a `.github/workflows` folder containing everything you need so that your tests run on each push and pull request into the main/master branch. Here's how that file looks: ```yml js title=".github/workflows/playwright.yml" name: Playwright Tests @@ -57,7 +51,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: lts/* - name: Install dependencies run: npm ci - name: Install Playwright Browsers @@ -72,10 +66,21 @@ jobs: retention-days: 30 ``` -### On push/pull_request +The workflow performs these steps: + +1. Clone your repository +2. Install Node.js +3. Install NPM Dependencies +4. Install Playwright Browsers +5. Run Playwright tests +6. Upload HTML report to the GitHub UI + +To learn more about this, see ["Understanding GitHub Actions"](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions). + +## Setting up GitHub Actions * langs: python, java, csharp -Tests will run on push or pull request on branches main/master. The [workflow](https://docs.github.com/en/actions/using-workflows/about-workflows) will install all dependencies, install Playwright and then run the tests. +To add a [GitHub Actions](https://docs.github.com/en/actions) file first create `.github/workflows` folder and inside it add a `playwright.yml` file containing the example code below so that your tests will run on each push and pull request for the main/master branch. ```yml python title=".github/workflows/playwright.yml" name: Playwright Tests @@ -128,7 +133,7 @@ jobs: java-version: '17' - name: Build & Install run: mvn -B install -D skipTests --no-transfer-progress - - name: Install Playwright + - name: Ensure browsers are installed run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" - name: Run tests run: mvn test @@ -151,275 +156,23 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - - run: dotnet build + - name: Build & Install + run: dotnet build - name: Ensure browsers are installed run: pwsh bin/Debug/net8.0/playwright.ps1 install --with-deps - name: Run your tests run: dotnet test ``` -### On push/pull_request (sharded) -* langs: js +To learn more about this, see ["Understanding GitHub Actions"](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions). -GitHub Actions supports [sharding tests between multiple jobs](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs). Check out our [sharding doc](./test-sharding) to learn more about sharding and to see a [GitHub actions example](./test-sharding.md#github-actions-example) of how to configure a job to run your tests on multiple machines as well as how to merge the HTML reports. +Looking at the list of steps in `jobs.test.steps`, you can see that the workflow performs these steps: -### Via Containers - -GitHub Actions support [running jobs in a container](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) by using the [`jobs..container`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainer) option. This is useful to not pollute the host environment with dependencies and to have a consistent environment for e.g. screenshots/visual regression testing across different operating systems. - -```yml js title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - playwright: - name: 'Playwright Tests' - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/playwright:v%%VERSION%%-jammy - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Run your tests - run: npx playwright test - env: - HOME: /root -``` - -```yml python title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - playwright: - name: 'Playwright Tests' - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/playwright/python:v%%VERSION%%-jammy - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r local-requirements.txt - pip install -e . - - name: Run your tests - run: pytest - env: - HOME: /root -``` - -```yml java title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - playwright: - name: 'Playwright Tests' - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/playwright/java:v%%VERSION%%-jammy - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '17' - - name: Build & Install - run: mvn -B install -D skipTests --no-transfer-progress - - name: Run tests - run: mvn test - env: - HOME: /root -``` - -```yml csharp title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - playwright: - name: 'Playwright Tests' - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/playwright/dotnet:v%%VERSION%%-jammy - steps: - - uses: actions/checkout@v4 - - name: Setup dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - run: dotnet build - - name: Run your tests - run: dotnet test - env: - HOME: /root -``` - -### On deployment - -This will start the tests after a [GitHub Deployment](https://developer.github.com/v3/repos/deployments/) went into the `success` state. -Services like Vercel use this pattern so you can run your end-to-end tests on their deployed environment. - -```yml js title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - deployment_status: -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - if: github.event.deployment_status.state == 'success' - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Install Playwright - run: npx playwright install --with-deps - - name: Run Playwright tests - run: npx playwright test - env: - PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} -``` - -```yml python title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - deployment_status: -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - if: github.event.deployment_status.state == 'success' - steps: - - uses: actions/checkout@v4 - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Ensure browsers are installed - run: python -m playwright install --with-deps - - name: Run tests - run: pytest - env: - # This might depend on your test-runner - PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} -``` - -```yml java title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - deployment_status: -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - if: github.event.deployment_status.state == 'success' - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '17' - - name: Build & Install - run: mvn -B install -D skipTests --no-transfer-progress - - name: Install Playwright - run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" - - name: Run tests - run: mvn test - env: - # This might depend on your test-runner - PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} -``` - -```yml csharp title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - deployment_status: -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - if: github.event.deployment_status.state == 'success' - steps: - - uses: actions/checkout@v4 - - name: Setup dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - run: dotnet build - - name: Ensure browsers are installed - run: pwsh bin/Debug/net8.0/playwright.ps1 install --with-deps - - name: Run tests - run: dotnet test - env: - # This might depend on your test-runner - PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} -``` - -### Fail-Fast -* langs: js - -Even with sharding enabled, large test suites can take very long to execute. Running changed test files first on PRs will give you a faster feedback loop and use less CI resources. - -```yml js title=".github/workflows/playwright.yml" -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run changed Playwright tests - run: npx playwright test --only-changed=$GITHUB_BASE_REF - if: github.event_name == 'pull_request' - - name: Run Playwright tests - run: npx playwright test - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 -``` +1. Clone your repository +2. Install language dependencies +3. Install project dependencies and build +4. Install Playwright Browsers +5. Run tests ## Create a Repo and Push to GitHub @@ -569,4 +322,5 @@ This step will not work for pull requests created from a forked repository becau - [Learn how to perform Actions](./input.md) - [Learn how to write Assertions](./test-assertions.md) - [Learn more about the Trace Viewer](/trace-viewer.md) -- [Learn more about running tests on other CI providers](/ci.md) +- [Learn more ways of running tests on GitHub Actions](/ci.md) +- [Learn more about running tests on other CI providers](/ci.md#github-actions) // TODO: is this link correct? \ No newline at end of file diff --git a/docs/src/ci.md b/docs/src/ci.md index 624ccfa447..7a08a00b4a 100644 --- a/docs/src/ci.md +++ b/docs/src/ci.md @@ -59,14 +59,398 @@ export default defineConfig({ }); ``` - ## CI configurations -The [Command line tools](./browsers#install-system-dependencies) can be used to install all operating system dependencies on GitHub Actions. +The [Command line tools](./browsers#install-system-dependencies) can be used to install all operating system dependencies in CI. ### GitHub Actions -Check out our [GitHub Actions](ci-intro.md) guide for more information on how to run your tests on GitHub. +#### On push/pull_request +* langs: js + +Tests will run on push or pull request on branches main/master. The [workflow](https://docs.github.com/en/actions/using-workflows/about-workflows) will install all dependencies, install Playwright and then run the tests. It will also create the HTML report. + +```yml js title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 +``` + +#### On push/pull_request +* langs: python, java, csharp + +Tests will run on push or pull request on branches main/master. The [workflow](https://docs.github.com/en/actions/using-workflows/about-workflows) will install all dependencies, install Playwright and then run the tests. + +```yml python title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Ensure browsers are installed + run: python -m playwright install --with-deps + - name: Run your tests + run: pytest --tracing=retain-on-failure + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-traces + path: test-results/ +``` + +```yml java title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Build & Install + run: mvn -B install -D skipTests --no-transfer-progress + - name: Ensure browsers are installed + run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" + - name: Run tests + run: mvn test +``` + +```yml csharp title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Build & Install + run: dotnet build + - name: Ensure browsers are installed + run: pwsh bin/Debug/net8.0/playwright.ps1 install --with-deps + - name: Run your tests + run: dotnet test +``` + +#### On push/pull_request (sharded) +* langs: js + +GitHub Actions supports [sharding tests between multiple jobs](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs). Check out our [sharding doc](./test-sharding) to learn more about sharding and to see a [GitHub actions example](./test-sharding.md#github-actions-example) of how to configure a job to run your tests on multiple machines as well as how to merge the HTML reports. + +#### Via Containers + +GitHub Actions support [running jobs in a container](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) by using the [`jobs..container`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainer) option. This is useful to not pollute the host environment with dependencies and to have a consistent environment for e.g. screenshots/visual regression testing across different operating systems. + +```yml js title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + playwright: + name: 'Playwright Tests' + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v%%VERSION%%-jammy + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Run your tests + run: npx playwright test + env: + HOME: /root +``` + +```yml python title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + playwright: + name: 'Playwright Tests' + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright/python:v%%VERSION%%-jammy + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r local-requirements.txt + pip install -e . + - name: Run your tests + run: pytest + env: + HOME: /root +``` + +```yml java title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + playwright: + name: 'Playwright Tests' + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright/java:v%%VERSION%%-jammy + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Build & Install + run: mvn -B install -D skipTests --no-transfer-progress + - name: Run tests + run: mvn test + env: + HOME: /root +``` + +```yml csharp title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + playwright: + name: 'Playwright Tests' + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright/dotnet:v%%VERSION%%-jammy + steps: + - uses: actions/checkout@v4 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - run: dotnet build + - name: Run your tests + run: dotnet test + env: + HOME: /root +``` + +#### On deployment + +This will start the tests after a [GitHub Deployment](https://developer.github.com/v3/repos/deployments/) went into the `success` state. +Services like Vercel use this pattern so you can run your end-to-end tests on their deployed environment. + +```yml js title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + deployment_status: +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + if: github.event.deployment_status.state == 'success' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + env: + PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} +``` + +```yml python title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + deployment_status: +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + if: github.event.deployment_status.state == 'success' + steps: + - uses: actions/checkout@v4 + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Ensure browsers are installed + run: python -m playwright install --with-deps + - name: Run tests + run: pytest + env: + # This might depend on your test-runner + PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} +``` + +```yml java title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + deployment_status: +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + if: github.event.deployment_status.state == 'success' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Build & Install + run: mvn -B install -D skipTests --no-transfer-progress + - name: Install Playwright + run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" + - name: Run tests + run: mvn test + env: + # This might depend on your test-runner + PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} +``` + +```yml csharp title=".github/workflows/playwright.yml" +name: Playwright Tests +on: + deployment_status: +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + if: github.event.deployment_status.state == 'success' + steps: + - uses: actions/checkout@v4 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - run: dotnet build + - name: Ensure browsers are installed + run: pwsh bin/Debug/net8.0/playwright.ps1 install --with-deps + - name: Run tests + run: dotnet test + env: + # This might depend on your test-runner + PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} +``` + +#### Fail-Fast +* langs: js + +Large test suites can take very long to execute. By executing a preliminary test run with the `--only-changed` flag, you can run test files that are likely to fail first. +This will give you a faster feedback loop and slightly lower CI consumption while working on Pull Requests. +To detect test files affected by your changeset, `--only-changed` analyses your suites' dependency graph. This is a heuristic and might miss tests, so it's important that you always run the full test suite after the preliminary test run. + +```yml js title=".github/workflows/playwright.yml" {20-23} +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run changed Playwright tests + run: npx playwright test --only-changed=$GITHUB_BASE_REF + if: github.event_name == 'pull_request' + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 +``` ### Docker @@ -660,4 +1044,4 @@ xvfb-run mvn test ``` ```bash csharp xvfb-run dotnet test -``` +``` \ No newline at end of file From b599335404e9e721565362c7332ffd5e565543f8 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 20 Aug 2024 14:16:28 +0200 Subject: [PATCH 13/25] chore(ui): enable `react/recommended` lint rules (#32214) Closes https://github.com/microsoft/playwright/issues/32159. I originally set out to enable Strict Mode for our React UI, but found a way better thing: Enabling the lint rules we had already installed! `eslint-plugin-react` is already in of our `package.json`, and this PR enables it and fixes some of the reported issues. Most of them are around the `key` prop which is mostly about performance, but there's also fixes for misspelled `data-testid` props. --- .eslintrc.js | 8 + package-lock.json | 861 +++++++++++------- package.json | 4 +- packages/html-reporter/src/icons.tsx | 8 +- packages/html-reporter/src/links.tsx | 2 +- packages/html-reporter/src/testCaseView.tsx | 2 +- packages/recorder/src/recorder.tsx | 2 +- .../trace-viewer/src/ui/attachmentsTab.tsx | 2 +- packages/trace-viewer/src/ui/consoleTab.tsx | 1 + .../trace-viewer/src/ui/networkFilters.tsx | 4 +- .../src/ui/networkResourceDetails.tsx | 2 +- packages/trace-viewer/src/ui/snapshotTab.tsx | 1 + packages/trace-viewer/src/ui/tag.tsx | 2 +- .../trace-viewer/src/ui/uiModeFiltersView.tsx | 4 +- packages/web/src/components/gridView.tsx | 2 + packages/web/src/components/listView.tsx | 1 + packages/web/src/components/tabbedPane.tsx | 3 +- packages/web/src/components/toolbarButton.tsx | 2 +- packages/web/src/shared/imageDiffView.tsx | 6 +- packages/web/src/shared/resizeView.tsx | 1 + .../ct-react-vite/src/components/Fetcher.tsx | 2 +- 21 files changed, 564 insertions(+), 356 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index bb351a8c56..a116a37036 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,9 +6,14 @@ module.exports = { sourceType: "module", }, extends: [ + "plugin:react/recommended", "plugin:react-hooks/recommended" ], + settings: { + react: { version: "18" } + }, + /** * ESLint rules * @@ -124,5 +129,8 @@ module.exports = { "mustMatch": "Copyright", "templateFile": require("path").join(__dirname, "utils", "copyright.js"), }], + + // react + "react/react-in-jsx-scope": 0 } }; diff --git a/package-lock.json b/package-lock.json index c88a0381a3..1c48c213d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,8 +47,8 @@ "eslint": "^8.55.0", "eslint-plugin-internal-playwright": "file:utils/eslint-plugin-internal-playwright", "eslint-plugin-notice": "^0.9.10", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^4.6.2", "formidable": "^2.1.1", "license-checker": "^25.0.1", "mime": "^3.0.0", @@ -2418,13 +2418,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2440,15 +2443,16 @@ } }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -2467,6 +2471,26 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", @@ -2504,30 +2528,34 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", - "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -2543,20 +2571,14 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "dependencies": { - "has-symbols": "^1.0.3" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -2720,14 +2742,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3039,6 +3066,57 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -3132,17 +3210,20 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -3280,50 +3361,57 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -3332,37 +3420,73 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "dev": true, "dependencies": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", + "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -3528,39 +3652,41 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "engines": { "node": ">=10" @@ -4096,16 +4222,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4126,13 +4256,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -4368,21 +4499,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -4404,12 +4535,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -4419,9 +4550,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -4524,12 +4655,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -4538,14 +4669,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4630,6 +4763,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -4703,18 +4851,21 @@ } }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -4781,21 +4932,27 @@ } }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4832,12 +4989,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -4847,10 +5004,13 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4868,13 +5028,16 @@ } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4891,6 +5054,12 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5467,28 +5636,29 @@ } }, "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5497,28 +5667,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5736,6 +5893,15 @@ "resolved": "packages/playwright-webkit", "link": true }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", @@ -5980,15 +6146,16 @@ "link": true }, "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -6006,14 +6173,15 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -6187,13 +6355,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", - "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -6204,20 +6372,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "engines": { @@ -6293,30 +6455,32 @@ } }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6344,14 +6508,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6508,34 +6676,51 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6545,28 +6730,31 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6767,29 +6955,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -6799,16 +6988,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -6818,14 +7008,20 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7463,13 +7659,13 @@ } }, "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", "dev": true, "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.0.5", "is-finalizationregistry": "^1.0.2", @@ -7478,8 +7674,8 @@ "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -7488,38 +7684,35 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index fa87e057cb..930fac8a80 100644 --- a/package.json +++ b/package.json @@ -85,8 +85,8 @@ "eslint": "^8.55.0", "eslint-plugin-internal-playwright": "file:utils/eslint-plugin-internal-playwright", "eslint-plugin-notice": "^0.9.10", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^4.6.2", "formidable": "^2.1.1", "license-checker": "^25.0.1", "mime": "^3.0.0", diff --git a/packages/html-reporter/src/icons.tsx b/packages/html-reporter/src/icons.tsx index 6111fac7c1..9609a2e23f 100644 --- a/packages/html-reporter/src/icons.tsx +++ b/packages/html-reporter/src/icons.tsx @@ -70,19 +70,19 @@ export const blank = () => { }; export const externalLink = () => { - return ; + return ; }; export const calendar = () => { - return ; + return ; }; export const person = () => { - return ; + return ; }; export const commit = () => { - return ; + return ; }; export const image = () => { diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index 55d7d24a6c..419a8725ac 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -81,7 +81,7 @@ export const AttachmentLink: React.FunctionComponent<{ {attachment.path && {linkName || attachment.name}} {!attachment.path && {linkifyText(attachment.name)}} } loadChildren={attachment.body ? () => { - return [
{linkifyText(attachment.body!)}
]; + return [
{linkifyText(attachment.body!)}
]; } : undefined} depth={0} style={{ lineHeight: '32px' }}>; }; diff --git a/packages/html-reporter/src/testCaseView.tsx b/packages/html-reporter/src/testCaseView.tsx index 4da49261d0..5bed3c8309 100644 --- a/packages/html-reporter/src/testCaseView.tsx +++ b/packages/html-reporter/src/testCaseView.tsx @@ -58,7 +58,7 @@ export const TestCaseView: React.FC<{ {labels && } } {!!visibleAnnotations.length && - {visibleAnnotations.map(annotation => )} + {visibleAnnotations.map((annotation, index) => )} } {test && ({ diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index b3966fd01a..486aeae701 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -171,7 +171,7 @@ export const Recorder: React.FC = ({ sidebarSize={200} main={} sidebar={ copy(locator)} />] : []} + rightToolbar={selectedTab === 'locator' ? [ copy(locator)} />] : []} tabs={[ { id: 'locator', diff --git a/packages/trace-viewer/src/ui/attachmentsTab.tsx b/packages/trace-viewer/src/ui/attachmentsTab.tsx index 8c72c7fee3..8b6cefe14d 100644 --- a/packages/trace-viewer/src/ui/attachmentsTab.tsx +++ b/packages/trace-viewer/src/ui/attachmentsTab.tsx @@ -126,7 +126,7 @@ export const AttachmentsTab: React.FunctionComponent<{ const url = attachmentURL(a); return ; })} {attachments.size ?
Attachments
: undefined} diff --git a/packages/trace-viewer/src/ui/consoleTab.tsx b/packages/trace-viewer/src/ui/consoleTab.tsx index a7b8318386..b2947f5011 100644 --- a/packages/trace-viewer/src/ui/consoleTab.tsx +++ b/packages/trace-viewer/src/ui/consoleTab.tsx @@ -213,6 +213,7 @@ function format(args: { preview: string, value: any }[]): JSX.Element[] { } function formatAnsi(text: string): JSX.Element[] { + // eslint-disable-next-line react/jsx-key return []; } diff --git a/packages/trace-viewer/src/ui/networkFilters.tsx b/packages/trace-viewer/src/ui/networkFilters.tsx index de6c827e2b..a88332e7c6 100644 --- a/packages/trace-viewer/src/ui/networkFilters.tsx +++ b/packages/trace-viewer/src/ui/networkFilters.tsx @@ -26,10 +26,10 @@ export type FilterState = { export const defaultFilterState: FilterState = { searchValue: '', resourceType: 'All' }; -export const NetworkFilters: React.FunctionComponent<{ +export const NetworkFilters = ({ filterState, onFilterStateChange }: { filterState: FilterState, onFilterStateChange: (filterState: FilterState) => void, -}> = ({ filterState, onFilterStateChange }) => { +}) => { return (
]} + leftToolbar={[]} tabs={[ { id: 'request', diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index 798025cbbd..4faa668677 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -184,6 +184,7 @@ export const SnapshotTab: React.FunctionComponent<{ setIsInspecting(!isInspecting)} /> {['action', 'before', 'after'].map(tab => { return void }> = ({ tag, style, onClick }) => { +export const TagView = ({ tag, style, onClick }: { tag: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }) => { return
{[...statusFilters.entries()].map(([status, value]) => { - return
+ return
{[...projectFilters.entries()].map(([projectName, value]) => { - return
+ return