Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run storybook tests with playwright #249

Merged
merged 39 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1ff6f82
resolve conflicts
ethangardner Jul 3, 2024
9ebd4f7
test cleanup
ethangardner Jul 5, 2024
bc30d9a
add post deployment action for testing
ethangardner Jul 5, 2024
261c2f9
update lockfile to fix failing action
ethangardner Jul 5, 2024
fc0438e
update action name
ethangardner Jul 5, 2024
9740619
correct config dir path
ethangardner Jul 5, 2024
f94d96d
add workflow dispatch for testing
ethangardner Jul 5, 2024
19b6a5a
add push action for wip branch
ethangardner Jul 5, 2024
6baa92d
fix failing test for radio group pattern edit
ethangardner Jul 3, 2024
f769587
update documentation and add script for storybook testing with playwr…
ethangardner Jul 3, 2024
cc94a2d
update lockfile
ethangardner Jul 5, 2024
3c1c60c
update path in action
ethangardner Jul 5, 2024
fabdd8b
set endpoint in action
ethangardner Jul 5, 2024
74eec1b
update workflow endpoint and run mode
ethangardner Jul 5, 2024
567f55a
install playwright in gh action
ethangardner Jul 5, 2024
534223c
create a self-contained image that runs through the tests on docker b…
ethangardner Jul 5, 2024
70ae3e9
use docker in post-deploy action
ethangardner Jul 5, 2024
26325c7
clean up logging for docker build
ethangardner Jul 5, 2024
2ee44c3
simplify end to end tests
ethangardner Jul 8, 2024
f11d4a8
debugging tests
ethangardner Jul 8, 2024
209ee88
rename test
ethangardner Jul 8, 2024
0b6bc26
install unfiltered deps in docker--no-verify
ethangardner Jul 8, 2024
f17f4aa
change end-to-end back to imported convention
ethangardner Jul 8, 2024
3c40e40
test deliberate failure in pipeline
ethangardner Jul 8, 2024
da6c75d
remove deliberately failing test
ethangardner Jul 8, 2024
117f9e0
update adr for test strategy
ethangardner Jul 8, 2024
d3091ea
add drag and drop test
ethangardner Jul 8, 2024
5aa5891
dry up test code
ethangardner Jul 8, 2024
9838d6a
add mouse interaction test for drag-and-drop
ethangardner Jul 8, 2024
7e9708f
fix flaky test
ethangardner Jul 9, 2024
a211bc4
remove flaky test
ethangardner Jul 9, 2024
56e193e
Merge branch 'main' into 106-playwright-storybook
ethangardner Jul 10, 2024
e493d19
remove commented code
ethangardner Jul 10, 2024
bbe040f
ignore pnpm cache dir
ethangardner Jul 11, 2024
0c7e9e9
use netcat instead of sleep to start server. pare down the copied fil…
ethangardner Jul 11, 2024
04917d1
use netcat instead of sleep to start server. pare down the copied fil…
ethangardner Jul 11, 2024
49219dd
add script to provide config for docker functions
ethangardner Jul 12, 2024
210379a
update documentation and add provisions for running stories locally
ethangardner Jul 12, 2024
9c53f42
grammar correction
ethangardner Jul 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/_end-to-end.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: End-to-end tests
on:
workflow_call:
jobs:
end-to-end:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker to run tests
run: |
docker build --platform linux/amd64 --tag 'playwright' . -f ./e2e/Dockerfile --target test
39 changes: 0 additions & 39 deletions .github/workflows/_playwright.yml

This file was deleted.

3 changes: 1 addition & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ jobs:
run-tests:
uses: ./.github/workflows/_validate.yml
e2e:
needs: [run-tests]
uses: ./.github/workflows/_playwright.yml
uses: ./.github/workflows/_end-to-end.yml
secrets: inherit
11 changes: 10 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
#!/bin/sh

pnpm lint
pnpm format
pnpm test
pnpm test

#command -v docker >/dev/null 2>&1 || { echo "Docker is not installed, skipping Docker commands." >&2; exit 0; }
#
#docker build --tag 'playwright' . -f ./e2e/Dockerfile --target test
#
## It's important to exit with 0 status code if the commit should not be aborted
ethangardner marked this conversation as resolved.
Show resolved Hide resolved
#exit 0
8 changes: 4 additions & 4 deletions documents/adr/0010-end-to-end-testing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 10. End to end testing
# 10. End-to-end and interaction testing

Date: 2024-07-01
Date: 2024-07-08

## Status

Expand All @@ -11,8 +11,8 @@ Pending
Certain tests are not able to be performed with Storybook and JSDOM (e.g. drag-and-drop). The ability to replicate more complex user interactions in the test suite through an actual browser can provide this feature.

## Decision
The end-to-end tests should be used sparingly since they are slower to run than the ones through JSDOM. Storybook still should be the primary mechanism for testing, and the Playwright tests will round out what isn't possible there.
The end-to-end tests should be used sparingly since they are slower to run than the ones through JSDOM. We will Playwright in CI/CD for the comprehensive tests and JSDOM during development for speed. Storybook still should be the primary mechanism for UI testing, and during CI/CD, the interaction tests will be run in Playwright using a docker container against the build.

ethangardner marked this conversation as resolved.
Show resolved Hide resolved
## Consequences

The deployed application will include Playwright tests in the e2e package.
There are some tests that will be end-to-end that run against the built application, while the interaction tests will run against the built Storybook. The end-to-end tests will be in the e2e directory, and docker will be used to make the test environment consistent.
9 changes: 6 additions & 3 deletions e2e/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ ENV NODE_ENV=test
WORKDIR /srv/apps/atj-platform
COPY . .
RUN corepack enable
RUN pnpm install --filter=@atj/spotlight --frozen-lockfile
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

FROM base as test
ENV E2E_ENDPOINT=http://localhost:9090
ENV CI=true
RUN npm install -g serve
EXPOSE 9090
RUN pnpm build --filter=@atj/spotlight
EXPOSE 9191
RUN pnpm build --filter=@atj/spotlight --filter=@atj/design
WORKDIR ./e2e
RUN serve ../apps/spotlight/dist -p 9090 & sleep 5; pnpm playwright test;
RUN serve ../apps/spotlight/dist -p 9090 -L & sleep 5; pnpm playwright test;
RUN serve ../packages/design/storybook-static -p 9191 -L & sleep 5; pnpm --filter=end-to-end-tests test:storybook --url http://localhost:9191 --config-dir ../packages/design/.storybook/ --browsers firefox chromium
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be some downsides of running the tests inside the Docker image build process vs at container runtime. It seems safer to run via the CMD (or ENTRYPOINT)... then, if we're using this image locally, we don't need to rebuild it when we rerun the tests.

Could these be defined in the package.json instead, so we can also run them on our local dev machine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielnaab - changes made. It's ready for you to take a look again. I created a bash script to help run the docker commands during local development that mounts a volume from the host machine so you can run it locally. I opted to keep this out of package.json because it's outside of the node ecosystem due to its dependency on Docker.


FROM base as serve
ENV E2E_ENDPOINT=http://localhost:4321
Expand Down
7 changes: 7 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ To debug and follow the flow of a test in a browser, you can run:
```bash
# run from this directory
export E2E_ENDPOINT=http://localhost:4321; pnpm playwright test --ui-port=8080 --ui-host=0.0.0.0
```

## Storybook Test Runner
This takes all the stories and runs them with Playwright. Start your dev server and then run:

```bash
pnpm --filter=end-to-end-tests test:storybook --url http://localhost:9009 --config-dir ../packages/design/.storybook/ --browsers firefox chromium
```
5 changes: 5 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
"version": "1.0.0",
"scripts": {
"dev": "tsc -w",
"test:storybook": "test-storybook",
"test": "export E2E_ENDPOINT=http://localhost:4321; pnpm playwright test --ui-port=8080 --ui-host=0.0.0.0"
},
"devDependencies": {
"@playwright/test": "^1.43.1",
"@storybook/test-runner": "^0.16.0",
"path-to-regexp": "^7.0.0"
},
"dependencies": {
"@atj/common": "workspace:*"
}
}
1 change: 1 addition & 0 deletions e2e/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const BASE_URL = process.env.E2E_ENDPOINT;
export const STORYBOOK_PATH = 'design/iframe.html?id=';
39 changes: 31 additions & 8 deletions e2e/src/create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import { pathToRegexp } from 'path-to-regexp';


const createNewForm = async (page: Page) => {
console.log(`${BASE_URL}/${GuidedFormCreation.getUrl()}`);
await page.goto(`${BASE_URL}/${GuidedFormCreation.getUrl()}`);
await page.getByRole('button', { name: 'Create New' }).click();
}

const addQuestions = async (page: Page) => {
const menuButton = page.getByRole('button', { name: 'Question' });
await menuButton.click();
await page.getByRole('button', { name: 'Short Answer' }).click();
await menuButton.click();
await page.getByRole('button', { name: 'Radio Buttons' }).click();
}

test('Create form from scratch', async ({ page }) => {
const regexp = pathToRegexp(Create.path);
await createNewForm(page);
Expand All @@ -30,16 +37,11 @@ test('Create form from scratch', async ({ page }) => {

test('Add questions', async ({ page }) => {
await createNewForm(page);

const menuButton = page.getByRole('button', { name: 'Question' });
await menuButton.click();
await page.getByRole('button', { name: 'Short Answer' }).click();
await menuButton.click();
await page.getByRole('button', { name: 'Radio Buttons' }).click();
await addQuestions(page);

// Create locators for both elements
const fields = page.locator('.usa-label');
const element1 = fields.filter({ hasText: 'Field Label' })
const element1 = fields.filter({ hasText: 'Field label' });
const element2 = fields.filter({ hasText: 'Radio group label' });
expect(element1.first()).toBeTruthy();
expect(element2.first()).toBeTruthy();
Expand All @@ -50,4 +52,25 @@ test('Add questions', async ({ page }) => {
const element2Index = htmlContent.indexOf((await element2.textContent() as string));
expect(element1Index).toBeLessThan(element2Index);

});

test('Drag-and-drop via mouse interaction', async ({ page }) => {
await createNewForm(page);
await addQuestions(page);

const handle = page.locator('[aria-describedby="DndDescribedBy-0"]').first();
await handle.hover();
await page.mouse.down();
const nextElement = page.locator('.draggable-list-item-wrapper').nth(1);
await nextElement.hover();
await page.mouse.up();

// Initiating a reorder clones the element. We want to await the count to go back to 1 to
// signify the invocation of the drag event has completed and the update has rendered.
await expect(page.getByText('Field label')).toHaveCount(1, {
timeout: 10000
});

await expect(page.locator('.draggable-list-item-wrapper').nth(1)).toContainText('Field label');

});
2 changes: 1 addition & 1 deletion e2e/src/my-forms.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import { BASE_URL } from './constants';
test('Go to MyForms', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/${MyForms.getUrl()}`);
expect(response?.ok()).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,11 @@ export const Basic: StoryObj<typeof FormEdit> = {
canvas.getByText(message.patterns.radioGroup.displayName)
);
const input = canvas.getByLabelText(message.patterns.radioGroup.fieldLabel);
const optionId = canvas.getByLabelText('Option 1 id');
const optionLabel = canvas.getByLabelText('Option 1 label');

// Enter new text for the field
await userEvent.clear(input);
await userEvent.type(input, updatedLabel);
await userEvent.clear(optionId);
await userEvent.type(optionId, 'yes');
await userEvent.clear(optionLabel);
await userEvent.type(optionLabel, 'Yes');

Expand All @@ -59,7 +56,7 @@ export const Basic: StoryObj<typeof FormEdit> = {
form?.requestSubmit();

await expect(await canvas.findByText(updatedLabel)).toBeInTheDocument();
await expect(await canvas.findByText('Yes')).toBeInTheDocument();
await expect(await canvas.findByText('Yes')).toBeVisible();
},
};

Expand Down Expand Up @@ -94,7 +91,6 @@ export const Error: StoryObj<typeof CheckboxPatternEdit> = {
);

const input = canvas.getByLabelText(message.patterns.radioGroup.fieldLabel);
const optionId = canvas.getByLabelText('Option 1 id');
const optionLabel = canvas.getByLabelText('Option 1 label');

// Clear input, remove focus, and wait for error
Expand All @@ -113,13 +109,6 @@ export const Error: StoryObj<typeof CheckboxPatternEdit> = {
*/
await userEvent.type(input, message.patterns.radioGroup.fieldLabel);

await userEvent.clear(optionId);
optionId.blur();

await expect(
await canvas.findByText('Invalid Option ID')
).toBeInTheDocument();

await userEvent.clear(optionLabel);
optionLabel.blur();

Expand Down
Loading
Loading