From cfe4a0ea085d2fc6328e93b838bff84cea16970a Mon Sep 17 00:00:00 2001 From: Andrew Huth Date: Tue, 23 Jul 2024 16:01:58 -0400 Subject: [PATCH 1/2] Add --port option --- CHANGELOG.md | 2 + README.md | 1 + src/Options.ts | 7 + src/Server.ts | 2 +- .../__snapshots__/integration.test.ts.snap | 215 ++++++++++++++++++ tests/integration/integration.test.ts | 16 ++ 6 files changed, 242 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0de0a2..7f8da68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [new] Add `--port` option + ## 8.0.2 - [fix] Ensure Storybook is ready before `extract`-ing stories [#94](https://github.com/chanzuckerberg/axe-storybook-testing/pull/94) diff --git a/README.md b/README.md index b0e98c3..e3ac6a2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ The command-line interface has the following options: | `--failing-impact` | `all` | all, minor, moderate, serious, critical | The lowest impact level that should be considered a failure | | `--headless` | `true` | boolean | Whether to run headlessly or not | | `--pattern` | `.*` | regex pattern | Only run tests that match a component name pattern | +| `--port` | | number | Port to run Storybook on while testing. If missing, an empty port will automatically be selected. Ignored if storybook-url is provided | `--reporter` | `spec` | spec, dot, nyan, tap, landing, list, progress, json, json-stream, min, doc, markdown, xunit | How to display the test run. Can be any [built-in Mocha reporter](https://mochajs.org/#reporters). | | `--reporter-options` | | string | Options to pass to the mocha reporter. Especially useful with the xunit reporter - e.g. `--reporter-options output=./filename.xml` | | `--storybook-address`| | url | **_Deprecated!_** Use `--storybook-url` instead. | diff --git a/src/Options.ts b/src/Options.ts index f10523b..4b8b388 100644 --- a/src/Options.ts +++ b/src/Options.ts @@ -50,6 +50,12 @@ const options = { description: 'Filter by a component name regex pattern', type: 'string' as const, }, + port: { + alias: 'P', + description: + 'Port to run Storybook on while testing. If missing, an empty port will automatically be selected. Ignored if storybook-url is provided', + type: 'number' as const, + }, reporter: { alias: 'r', default: 'spec' as Reporters, @@ -112,6 +118,7 @@ export function parseOptions() { headless: argv.headless, failingImpacts: getFailingImpacts(argv['failing-impact']), pattern: new RegExp(argv.pattern), + port: argv.port, reporter: argv.reporter, reporterOptions: getReporterOptions(argv['reporter-options']), storybookUrl: argv.storybookAddress || argv.storybookUrl, diff --git a/src/Server.ts b/src/Server.ts index a737677..964db31 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -48,7 +48,7 @@ async function getServer(options: Options): Promise { await waitRandomTime(500); const localPath = getStaticStorybookPath(options); - const port = await portfinder.getPortPromise(); + const port = options.port || (await portfinder.getPortPromise()); const host = '127.0.0.1'; const server = httpServer.createServer({root: localPath}); const storybookUrl = `http://${host}:${port}`; diff --git a/tests/integration/__snapshots__/integration.test.ts.snap b/tests/integration/__snapshots__/integration.test.ts.snap index 56e7520..540cab6 100644 --- a/tests/integration/__snapshots__/integration.test.ts.snap +++ b/tests/integration/__snapshots__/integration.test.ts.snap @@ -1,5 +1,220 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`accepts a port to run on 1`] = ` +" +> storybook-for-react-integration-tests@3.1.0 storybook:axeOnly +> axe-storybook --port 8111 + +Serving up 🍕 static storybook build at: http://127.0.0.1:8111 + +[chromium] accessibility + advanced + 1) Branding + + autoTitles + ✔ No Failures + + delays + ✔ Short Delay And Pass + 2) Short Delay And Fail + ✔ Medium Delay And Pass + 3) Medium Delay And Fail + 4) Medium Delay And Short Timeout Fail + 5) Long Delay And Timeout + + simple + ✔ No Failures + 6) Failure No Discernible Text + 7) Failure Color Contrast + 8) Failure No Discernible Text And Invalid Role + - Failure Color Contrast Skipped + - No Failures Warn + - Failure Color Contrast Warn + - Failure No Discernible Text Warn + - Failure Color Contrast Off + - Failure No Discernible Text And Invalid Role Skipped + ✔ Failure Color Contrast Disabled Rule + 9) Failure No Discernible Text And Invalid Role Disabled One Rule + ✔ Failure No Discernible Text And Invalid Role Disabled Rules + +2 violations were detected in stories with "mode" set to "warn", so did not fail the test suite: + + Error: simple / Failure Color Contrast Warn / Detected the following accessibility violations! + + 1. color-contrast (Elements must meet minimum color contrast ratio thresholds) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 + +Error: simple / Failure No Discernible Text Warn / Detected the following accessibility violations! + + 1. button-name (Buttons must have discernible text) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/button-name?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element does not have inner text that is visible to screen readers + aria-label attribute does not exist or is empty + aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty + Element has no title attribute + Element's default semantics were not overridden with role="none" or role="presentation" + + 6 passing + 6 pending + 9 failing + + 1) advanced + Branding: + Detected the following accessibility violations! + + 1. color-contrast (Elements must meet minimum color contrast ratio thresholds) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=my-branding. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 + + + 2) delays + Short Delay And Fail: + Detected the following accessibility violations! + + 1. aria-roles (ARIA roles used must conform to valid values) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/aria-roles?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix all of the following: + Role must be one of the valid ARIA roles: wut-the-wut + + + 3) delays + Medium Delay And Fail: + Detected the following accessibility violations! + + 1. aria-roles (ARIA roles used must conform to valid values) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/aria-roles?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix all of the following: + Role must be one of the valid ARIA roles: wut-the-wut + + + 4) delays + Medium Delay And Short Timeout Fail: + Error: Timeout of 100ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. + at listOnTimeout (node:internal/timers) + at processTimers (node:internal/timers) + + 5) delays + Long Delay And Timeout: + Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. + at listOnTimeout (node:internal/timers) + at processTimers (node:internal/timers) + + 6) simple + Failure No Discernible Text: + Detected the following accessibility violations! + + 1. button-name (Buttons must have discernible text) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/button-name?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element does not have inner text that is visible to screen readers + aria-label attribute does not exist or is empty + aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty + Element has no title attribute + Element's default semantics were not overridden with role="none" or role="presentation" + + + 7) simple + Failure Color Contrast: + Detected the following accessibility violations! + + 1. color-contrast (Elements must meet minimum color contrast ratio thresholds) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 + + + 8) simple + Failure No Discernible Text And Invalid Role: + Detected the following accessibility violations! + + 1. aria-roles (ARIA roles used must conform to valid values) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/aria-roles?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix all of the following: + Role must be one of the valid ARIA roles: wut-the-wut + + 2. button-name (Buttons must have discernible text) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/button-name?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element does not have inner text that is visible to screen readers + aria-label attribute does not exist or is empty + aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty + Element has no title attribute + Element's default semantics were not overridden with role="none" or role="presentation" + + + 9) simple + Failure No Discernible Text And Invalid Role Disabled One Rule: + Detected the following accessibility violations! + + 1. button-name (Buttons must have discernible text) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/button-name?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element does not have inner text that is visible to screen readers + aria-label attribute does not exist or is empty + aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty + Element has no title attribute + Element's default semantics were not overridden with role="none" or role="presentation" + + + + +" +`; + exports[`fails only specific impact levels if specified 1`] = ` " > storybook-for-react-integration-tests@3.1.0 storybook:axeOnly diff --git a/tests/integration/integration.test.ts b/tests/integration/integration.test.ts index fdef11a..78fa719 100644 --- a/tests/integration/integration.test.ts +++ b/tests/integration/integration.test.ts @@ -46,6 +46,22 @@ it('fails only specific impact levels if specified', () => { }); }, 120_000); +it('accepts a port to run on', () => { + expect.assertions(2); + + return new Promise((done) => { + exec( + 'cd demo && npm run storybook:axeOnly -- --port 8111', + function (error, stdout) { + const normalizedStdout = normalize(stdout); + expect(error!.code).toEqual(1); + expect(normalizedStdout).toMatchSnapshot(); + done(); + }, + ); + }); +}, 120_000); + /** * Remove items from a string that are specific to a test run or environment, such as timing * information and file-system paths. That way, we can snapshot test effectively. From 2eac16615aaf073d8112f80b1fd1f03a7e8b695f Mon Sep 17 00:00:00 2001 From: Andrew Huth Date: Tue, 23 Jul 2024 17:06:42 -0400 Subject: [PATCH 2/2] Find a new port if the specified --port is taken --- src/Server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server.ts b/src/Server.ts index 964db31..345b9f9 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -48,7 +48,7 @@ async function getServer(options: Options): Promise { await waitRandomTime(500); const localPath = getStaticStorybookPath(options); - const port = options.port || (await portfinder.getPortPromise()); + const port = await portfinder.getPortPromise({port: options.port}); const host = '127.0.0.1'; const server = httpServer.createServer({root: localPath}); const storybookUrl = `http://${host}:${port}`;