From 3e836bf0aa04354e307e9410c4b8973c1496e072 Mon Sep 17 00:00:00 2001 From: ryanrosello-og <50574915+ryanrosello-og@users.noreply.github.com> Date: Sat, 6 Jan 2024 20:48:04 +1000 Subject: [PATCH] Fix cli broken parser (#81) * Fix showInThread support for webhooks * Bump version to 1.1.58 in package.json * Fix really broken json results parsers * Add test to parse complicated json file * add suite name to the failure message --- package.json | 2 +- src/LayoutGenerator.ts | 4 +- src/ResultsParser.ts | 45 +-- src/SlackReporter.ts | 8 + src/cli/cli_pre_checks.ts | 8 + src/index.ts | 1 + tests/ResultsParser.spec.ts | 20 +- tests/SlackClient_generate_blocks.spec.ts | 20 +- tests/SlackReporter.spec.ts | 22 ++ tests/SlackWehookClient.spec.ts | 3 +- tests/cli_prechecks.spec.ts | 56 ++- tests/fixtures.ts | 1 + ...onfig_showInThread_enable_for_webhook.json | 16 + .../test_data/valid_test_results_complex.json | 361 ++++++++++++++++++ 14 files changed, 495 insertions(+), 72 deletions(-) create mode 100644 tests/test_data/invalid_cli_config_showInThread_enable_for_webhook.json create mode 100644 tests/test_data/valid_test_results_complex.json diff --git a/package.json b/package.json index 481c55b..f4d81c3 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "cli-debug": "yarn build && npx . -c ./cli_config.json -j ./tests/test_data/valid_test_results.json" }, "name": "playwright-slack-report", - "version": "1.1.57", + "version": "1.1.58", "bin": { "playwright-slack-report": "dist/cli.js" }, diff --git a/src/LayoutGenerator.ts b/src/LayoutGenerator.ts index b52bea9..2f255f6 100644 --- a/src/LayoutGenerator.ts +++ b/src/LayoutGenerator.ts @@ -56,7 +56,7 @@ const generateFailures = async ( ); for (let i = 0; i < numberOfFailuresToShow; i += 1) { - const { failureReason, test } = summaryResults.failures[i]; + const { failureReason, test, suite } = summaryResults.failures[i]; const formattedFailure = failureReason .substring(0, maxNumberOfFailureLength) .split('\n') @@ -66,7 +66,7 @@ const generateFailures = async ( type: 'section', text: { type: 'mrkdwn', - text: `*${test}* + text: `*${suite} > ${test}* \n${formattedFailure}`, }, }); diff --git a/src/ResultsParser.ts b/src/ResultsParser.ts index 6c91f1b..07b0748 100644 --- a/src/ResultsParser.ts +++ b/src/ResultsParser.ts @@ -51,7 +51,10 @@ export default class ResultsParser { const parsedData: JSONResult = JSON.parse(data); const retries = parsedData.config.projects[0]?.retries || 0; - await this.parseTestSuite(parsedData.suites, retries); + for (const suite of parsedData.suites) { + // eslint-disable-next-line no-await-in-loop + await this.parseTestSuite(suite, retries); + } const failures = await this.getFailures(); const summary: SummaryResults = { @@ -66,44 +69,33 @@ export default class ResultsParser { for (const suite of this.result) { summary.tests = summary.tests.concat(suite.testSuite.tests); } - // console.log('🚀~ summary:', JSON.stringify(summary, null, 2)); return summary; } - async parseTestSuite(suites: any, retries: number, suiteIndex = 0) { + async parseTestSuite(suites: any, retries: number) { let testResults = []; - if (suites[0].suites?.length > 0) { - testResults = await this.parseTests( - suites[0].title, - suites[0].specs, - retries, - ); - this.updateResults({ - testSuite: { - title: suites[0].title, - tests: testResults, - }, - }); - await this.parseTestSuite( - suites[suiteIndex].suites, - retries, - (suiteIndex += 1), - ); - } else { + + // if it has direct specs + if (suites.specs?.length > 0) { testResults = await this.parseTests( - suites[0].title, - suites[0].specs, + suites.title, + suites.specs, retries, ); this.updateResults({ testSuite: { - title: suites[0].title, + title: suites.title ?? suites.file, tests: testResults, }, }); - // eslint-disable-next-line no-useless-return - return; + } + + if (suites.suites?.length > 0) { + for (const suite of suites.suites) { + // eslint-disable-next-line no-await-in-loop + await this.parseTestSuite(suite, retries); + } } } @@ -175,6 +167,7 @@ export default class ResultsParser { // only flag as failed if the last attempt has failed if (test.retries === test.retry) { failures.push({ + suite: test.suiteName, test: ResultsParser.getTestName(test), failureReason: test.reason, }); diff --git a/src/SlackReporter.ts b/src/SlackReporter.ts index d887ce3..c7e22b6 100644 --- a/src/SlackReporter.ts +++ b/src/SlackReporter.ts @@ -189,6 +189,14 @@ class SlackReporter implements Reporter { }; } + if (this.slackWebHookUrl && this.showInThread) { + return { + okToProceed: false, + message: + '❌ The showInThread feature is only supported when using slackOAuthToken or process.env.SLACK_BOT_USER_OAUTH_TOKEN', + }; + } + if ( !this.sendResults || !['always', 'on-failure', 'off'].includes(this.sendResults) diff --git a/src/cli/cli_pre_checks.ts b/src/cli/cli_pre_checks.ts index 0cd03d1..5ca069c 100644 --- a/src/cli/cli_pre_checks.ts +++ b/src/cli/cli_pre_checks.ts @@ -55,6 +55,14 @@ export const doPreChecks = async ( }; } + if (config.sendUsingWebhook && config.showInThread) { + return { + status: 'error', + message: + 'The showInThread feature is only supported when using sendUsingBot is configured', + }; + } + if (!config.sendUsingWebhook && !config.sendUsingBot) { return { status: 'error', diff --git a/src/index.ts b/src/index.ts index a11678a..54734d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ export type SummaryResults = { export type Meta = Array<{ key: string; value: string }>; export type failure = { + suite: string; test: string; failureReason: string; }; diff --git a/tests/ResultsParser.spec.ts b/tests/ResultsParser.spec.ts index b9c27eb..83085f8 100644 --- a/tests/ResultsParser.spec.ts +++ b/tests/ResultsParser.spec.ts @@ -508,7 +508,7 @@ test.describe('ResultsParser', () => { ).toEqual('Login [Project Name: nightly_regression] using chrome'); }); - test('parse test results from json file', async ({}) => { + test('parse test results from json file that has retries, flakies and skipped tests', async ({}) => { const resultsParser = new ResultsParser(); const validTestResults = path.join( __dirname, @@ -525,4 +525,22 @@ test.describe('ResultsParser', () => { expect(resultSummary.failures.length).toEqual(3); expect(resultSummary.tests.length).toEqual(10); }); + + test('parse test results from a complicated json file', async ({}) => { + const resultsParser = new ResultsParser(); + const validTestResults = path.join( + __dirname, + 'test_data', + 'valid_test_results_complex.json', + ); + const resultSummary = await resultsParser.parseFromJsonFile( + validTestResults, + ); + expect(resultSummary.failed).toEqual(1); + expect(resultSummary.flaky).toEqual(0); + expect(resultSummary.skipped).toEqual(0); + expect(resultSummary.passed).toEqual(6); + expect(resultSummary.failures.length).toEqual(1); + expect(resultSummary.tests.length).toEqual(7); + }); }); diff --git a/tests/SlackClient_generate_blocks.spec.ts b/tests/SlackClient_generate_blocks.spec.ts index 9b513cf..d48774f 100644 --- a/tests/SlackClient_generate_blocks.spec.ts +++ b/tests/SlackClient_generate_blocks.spec.ts @@ -10,9 +10,9 @@ test.describe('SlackClient.generateBlocks()', () => { flaky: undefined, skipped: 0, failures: [ - { test: 'test', failureReason: 'message' }, - { test: 'test2', failureReason: 'message' }, - { test: 'test3', failureReason: 'message' }, + { suite: 'smoke', test: 'test', failureReason: 'message' }, + { suite: 'smoke', test: 'test2', failureReason: 'message' }, + { suite: 'smoke', test: 'test3', failureReason: 'message' }, ], tests: [], }, @@ -35,9 +35,9 @@ test.describe('SlackClient.generateBlocks()', () => { flaky: undefined, skipped: 0, failures: [ - { test: 'test', failureReason: 'message' }, - { test: 'test2', failureReason: 'message' }, - { test: 'test3', failureReason: 'message' }, + { suite: 'smoke', test: 'test', failureReason: 'message' }, + { suite: 'smoke', test: 'test2', failureReason: 'message' }, + { suite: 'smoke', test: 'test3', failureReason: 'message' }, ], tests: [], }, @@ -59,7 +59,7 @@ test.describe('SlackClient.generateBlocks()', () => { passed: 1, flaky: undefined, skipped: 1, - failures: [{ test: 'test', failureReason: 'message' }], + failures: [{ suite: 'smoke', test: 'test', failureReason: 'message' }], tests: [], }, 2, @@ -86,7 +86,7 @@ test.describe('SlackClient.generateBlocks()', () => { type: 'section', text: { type: 'mrkdwn', - text: '*test*\n \n>message', + text: '*smoke > test*\n \n>message', }, }, ]); @@ -99,7 +99,7 @@ test.describe('SlackClient.generateBlocks()', () => { passed: 1, flaky: 1, skipped: 1, - failures: [{ test: 'test', failureReason: 'message' }], + failures: [{ suite: 'smoke', test: 'test', failureReason: 'message' }], tests: [], }, 2, @@ -126,7 +126,7 @@ test.describe('SlackClient.generateBlocks()', () => { type: 'section', text: { type: 'mrkdwn', - text: '*test*\n \n>message', + text: '*smoke > test*\n \n>message', }, }, ]); diff --git a/tests/SlackReporter.spec.ts b/tests/SlackReporter.spec.ts index f7104f5..faeb43f 100644 --- a/tests/SlackReporter.spec.ts +++ b/tests/SlackReporter.spec.ts @@ -261,6 +261,28 @@ test.describe('SlackReporter - preChecks()', () => { }); }); + test('showInThread only supported for bots not webhooks', async ({ + fakeSlackReporter, + suite, + fullConfig, + }) => { + delete process.env.SLACK_BOT_USER_OAUTH_TOKEN; + const cloneFullConfig = JSON.parse(JSON.stringify(fullConfig)); + cloneFullConfig.reporter = [ + [ + '/home/ry/_repo/playwright-slack-report/src/SlackReporter.ts', + { slackWebHookUrl: 'https://hooks.slack.com/services/1234', showInThread: true }, + ], + ]; + fakeSlackReporter.onBegin(cloneFullConfig, suite); + + const result = fakeSlackReporter.preChecks(); + expect(result).toEqual({ + okToProceed: false, + message: '❌ The showInThread feature is only supported when using slackOAuthToken or process.env.SLACK_BOT_USER_OAUTH_TOKEN', + }); + }); + test('okToProceed flag is set to false when the custom layout provided is not a Function', async ({ fakeSlackReporter, suite, diff --git a/tests/SlackWehookClient.spec.ts b/tests/SlackWehookClient.spec.ts index 0735179..8c703e8 100644 --- a/tests/SlackWehookClient.spec.ts +++ b/tests/SlackWehookClient.spec.ts @@ -12,6 +12,7 @@ const test = base.extend<{ summaryResults: SummaryResults }>({ skipped: 1, failures: [ { + suite: 'smoke', test: 'test', failureReason: 'Unexpected error', }, @@ -86,7 +87,7 @@ test.describe('SlackWebhookClient.sendMessage()', () => { type: 'section', text: { type: 'mrkdwn', - text: '*test*\n \n>Unexpected error', + text: '*smoke > test*\n \n>Unexpected error', }, }, ], diff --git a/tests/cli_prechecks.spec.ts b/tests/cli_prechecks.spec.ts index dfc486f..40a2279 100644 --- a/tests/cli_prechecks.spec.ts +++ b/tests/cli_prechecks.spec.ts @@ -2,6 +2,11 @@ import { expect, test } from '@playwright/test'; import path from 'path'; import doPreChecks from '../src/cli/cli_pre_checks'; +const validTestResults = path.join( + __dirname, + 'test_data', + 'valid_test_results.json', +); test.describe('CLI app - pre-check', () => { test.beforeAll(async ({}) => {}); @@ -15,22 +20,12 @@ test.describe('CLI app - pre-check', () => { }); test('throws an error when the config file does not exist', async ({}) => { - const validTestResults = path.join( - __dirname, - 'test_data', - 'valid_test_results.json', - ); const result = await doPreChecks(validTestResults, 'does-not-exist.json'); expect(result.status).toEqual('error'); expect(result.message).toContain('Config file does not exist'); }); test('throws an error when both sendUsingBot and sendUsingWebhook defined', async ({}) => { - const validTestResults = path.join( - __dirname, - 'test_data', - 'valid_test_results.json', - ); const invalidConfig = path.join( __dirname, 'test_data', @@ -38,15 +33,25 @@ test.describe('CLI app - pre-check', () => { ); const result = await doPreChecks(validTestResults, invalidConfig); expect(result.status).toEqual('error'); - expect(result.message).toContain('It is not possible to use both sendUsingWebhook and sendUsingBot, choose a single method'); + expect(result.message).toContain( + 'It is not possible to use both sendUsingWebhook and sendUsingBot, choose a single method', + ); }); - test('throws an error when missing both sendUsingBot and sendUsingWebhook keys', async ({}) => { - const validTestResults = path.join( + test('throws an error when both sendUsingWebhook and showInThread is true', async ({}) => { + const invalidConfig = path.join( __dirname, 'test_data', - 'valid_test_results.json', + 'invalid_cli_config_showInThread_enable_for_webhook.json', + ); + const result = await doPreChecks(validTestResults, invalidConfig); + expect(result.status).toEqual('error'); + expect(result.message).toContain( + 'The showInThread feature is only supported when using sendUsingBot is configured', ); + }); + + test('throws an error when missing both sendUsingBot and sendUsingWebhook keys', async ({}) => { const invalidConfig = path.join( __dirname, 'test_data', @@ -54,7 +59,9 @@ test.describe('CLI app - pre-check', () => { ); const result = await doPreChecks(validTestResults, invalidConfig); expect(result.status).toEqual('error'); - expect(result.message).toContain('You must specify either sendUsingWebhook or sendUsingBot in the config file'); + expect(result.message).toContain( + 'You must specify either sendUsingWebhook or sendUsingBot in the config file', + ); }); test('zod parsing throws an error when cli config misconfigured', async ({}) => { @@ -63,11 +70,6 @@ test.describe('CLI app - pre-check', () => { 'test_data', 'invalid_cli_config_fails_zod_parsing.json', ); - const validTestResults = path.join( - __dirname, - 'test_data', - 'valid_test_results.json', - ); const result = await doPreChecks(validTestResults, invalidConfig); expect(result.status).toEqual('error'); expect(result.message).toEqual(`Config file is not valid: [ @@ -90,15 +92,12 @@ test.describe('CLI app - pre-check', () => { 'test_data', 'valid_cli_config.json', ); - const validTestResults = path.join( - __dirname, - 'test_data', - 'valid_test_results.json', - ); delete process.env.SLACK_BOT_USER_OAUTH_TOKEN; const result = await doPreChecks(validTestResults, validConfig); expect(result.status).toEqual('error'); - expect(result.message).toEqual('Missing the SLACK_BOT_USER_OAUTH_TOKEN env variable'); + expect(result.message).toEqual( + 'Missing the SLACK_BOT_USER_OAUTH_TOKEN env variable', + ); }); test('provides config value when all pre-checks are valid', async ({}) => { @@ -107,11 +106,6 @@ test.describe('CLI app - pre-check', () => { 'test_data', 'valid_cli_config.json', ); - const validTestResults = path.join( - __dirname, - 'test_data', - 'valid_test_results.json', - ); process.env.SLACK_BOT_USER_OAUTH_TOKEN = 'test'; const result = await doPreChecks(validTestResults, validConfig); expect(result.status).toEqual('ok'); diff --git a/tests/fixtures.ts b/tests/fixtures.ts index b02dbd7..069e3ab 100644 --- a/tests/fixtures.ts +++ b/tests/fixtures.ts @@ -44,6 +44,7 @@ export const test = base.extend({ skipped: 0, failures: [ { + suite: 'tests/t1.spec.ts', test: 'basic test failure', failureReason: 'expect(received).toHaveText(expected)\n\nExpected string: "Playwright Fail"\nReceived string: "Playwright"\nCall log:\n - expect.toHaveText with timeout 1000ms\n - waiting for selector ".navbar__inner .navbar__title"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n \n Error: expect(received).toHaveText(expected)\n\nExpected string: "Playwright Fail"\nReceived string: "Playwright"\nCall log:\n - expect.toHaveText with timeout 1000ms\n - waiting for selector ".navbar__inner .navbar__title"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n - selector resolved to Playwright\n - unexpected value "Playwright"\n\n at /home/ry/_repo/playwright-slack-report/tests/t1.spec.ts:17:23', diff --git a/tests/test_data/invalid_cli_config_showInThread_enable_for_webhook.json b/tests/test_data/invalid_cli_config_showInThread_enable_for_webhook.json new file mode 100644 index 0000000..4bb84c2 --- /dev/null +++ b/tests/test_data/invalid_cli_config_showInThread_enable_for_webhook.json @@ -0,0 +1,16 @@ +{ + "sendResults": "always", + "slackLogLevel": "error", + "showInThread": true, + "meta": [ + { "key": "build", "value" : "1.0.0"}, + { "key": "branch", "value" : "master"}, + { "key": "commit", "value" : "1234567890"}, + { "key": "results", "value" : "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"} + ], + "maxNumberOfFailures": 1, + "disableUnfurl": true, + "sendUsingWebhook": { + "webhookUrl":"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" + } +} \ No newline at end of file diff --git a/tests/test_data/valid_test_results_complex.json b/tests/test_data/valid_test_results_complex.json new file mode 100644 index 0000000..97366cf --- /dev/null +++ b/tests/test_data/valid_test_results_complex.json @@ -0,0 +1,361 @@ +{ + "config": { + "configFile": "C:\\_tmp\\pw_json\\playwright.config.ts", + "rootDir": "C:/_tmp/pw_json/tests", + "forbidOnly": false, + "fullyParallel": true, + "globalSetup": null, + "globalTeardown": null, + "globalTimeout": 0, + "grep": {}, + "grepInvert": null, + "maxFailures": 0, + "metadata": { + "actualWorkers": 7 + }, + "preserveOutput": "always", + "reporter": [ + [ + "json", + null + ] + ], + "reportSlowTests": { + "max": 5, + "threshold": 15000 + }, + "quiet": false, + "projects": [ + { + "outputDir": "C:/_tmp/pw_json/test-results", + "repeatEach": 1, + "retries": 0, + "id": "", + "name": "", + "testDir": "C:/_tmp/pw_json/tests", + "testIgnore": [], + "testMatch": [ + "**/*.@(spec|test).?(c|m)[jt]s?(x)" + ], + "timeout": 30000 + } + ], + "shard": null, + "updateSnapshots": "missing", + "version": "1.40.1", + "workers": 16, + "webServer": null + }, + "suites": [ + { + "title": "example_two.spec.ts", + "file": "example_two.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "standard suiTE", + "file": "example_two.spec.ts", + "line": 3, + "column": 6, + "specs": [ + { + "title": "normal", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 0, + "status": "passed", + "duration": 286, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.852Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "ae26aadcfef1273f4895-e4a5df1c19617f462932", + "file": "example_two.spec.ts", + "line": 4, + "column": 7 + }, + { + "title": "has failure", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 1, + "status": "failed", + "duration": 249, + "error": { + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at C:\\_tmp\\pw_json\\tests\\example_two.spec.ts:9:18", + "location": { + "file": "C:\\_tmp\\pw_json\\tests\\example_two.spec.ts", + "column": 18, + "line": 9 + }, + "snippet": "\u001b[0m \u001b[90m 7 |\u001b[39m test(\u001b[32m\"has failure\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 8 |\u001b[39m \u001b[90m//\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 9 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 10 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 11 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 12 |\u001b[39m\u001b[0m" + }, + "errors": [ + { + "location": { + "file": "C:\\_tmp\\pw_json\\tests\\example_two.spec.ts", + "column": 18, + "line": 9 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[0m \u001b[90m 7 |\u001b[39m test(\u001b[32m\"has failure\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 8 |\u001b[39m \u001b[90m//\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 9 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 10 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 11 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 12 |\u001b[39m\u001b[0m\n\n\u001b[2m at C:\\_tmp\\pw_json\\tests\\example_two.spec.ts:9:18\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.845Z", + "attachments": [], + "errorLocation": { + "file": "C:\\_tmp\\pw_json\\tests\\example_two.spec.ts", + "column": 18, + "line": 9 + } + } + ], + "status": "unexpected" + } + ], + "id": "ae26aadcfef1273f4895-03c2041220b0cd54dbd7", + "file": "example_two.spec.ts", + "line": 7, + "column": 7 + } + ] + } + ] + }, + { + "title": "example.spec.ts", + "file": "example.spec.ts", + "column": 0, + "line": 0, + "specs": [ + { + "title": "has title A", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 2, + "status": "passed", + "duration": 251, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.837Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-ba96f2305594a9252d4d", + "file": "example.spec.ts", + "line": 3, + "column": 5 + }, + { + "title": "has title B", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 3, + "status": "passed", + "duration": 247, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.865Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-80138906bc949d4232be", + "file": "example.spec.ts", + "line": 6, + "column": 5 + } + ], + "suites": [ + { + "title": "group SUITE", + "file": "example.spec.ts", + "line": 10, + "column": 6, + "specs": [ + { + "title": "inside SUITE nested", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 4, + "status": "passed", + "duration": 264, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.847Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-35c32c02e35d11a2ec51", + "file": "example.spec.ts", + "line": 11, + "column": 7 + } + ], + "suites": [ + { + "title": "real innie SUITE", + "file": "example.spec.ts", + "line": 15, + "column": 8, + "specs": [ + { + "title": "real innie test nested", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 5, + "status": "passed", + "duration": 211, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.858Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-c681fce9da5f8509ff0c", + "file": "example.spec.ts", + "line": 16, + "column": 9 + } + ] + } + ] + }, + { + "title": "multi SUITE", + "file": "example.spec.ts", + "line": 22, + "column": 6, + "specs": [ + { + "title": "single test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 30000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "", + "projectName": "", + "results": [ + { + "workerIndex": 6, + "status": "passed", + "duration": 200, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2024-01-06T09:57:13.863Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-dac63ebdd682cc43f554", + "file": "example.spec.ts", + "line": 23, + "column": 7 + } + ] + } + ] + } + ], + "errors": [], + "stats": { + "startTime": "2024-01-06T09:57:13.004Z", + "duration": 2278.9300000000003, + "expected": 6, + "skipped": 0, + "unexpected": 1, + "flaky": 0 + } +} \ No newline at end of file