Skip to content

Commit e9a9a8b

Browse files
authored
test(quay): UI test update and a11y check (#6250)
* test(quay): UI test update and a11y check Signed-off-by: Jan Richter <[email protected]> * add filtering cases Signed-off-by: Jan Richter <[email protected]> --------- Signed-off-by: Jan Richter <[email protected]>
1 parent 2b8134c commit e9a9a8b

File tree

6 files changed

+175
-56
lines changed

6 files changed

+175
-56
lines changed

workspaces/quay/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@backstage/repo-tools": "^0.15.3",
4444
"@changesets/cli": "^2.27.1",
4545
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
46+
"@playwright/test": "^1.56.1",
4647
"knip": "^5.27.4",
4748
"node-gyp": "^9.0.0",
4849
"prettier": "^2.3.2",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2025 The Backstage Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { defineConfig } from '@playwright/test';
17+
18+
export default defineConfig({
19+
webServer: process.env.PLAYWRIGHT_URL
20+
? []
21+
: {
22+
command: 'yarn start',
23+
cwd: 'plugins/quay',
24+
port: 3000,
25+
reuseExistingServer: true,
26+
},
27+
28+
retries: process.env.CI ? 2 : 0,
29+
30+
reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]],
31+
32+
use: {
33+
baseURL: process.env.PLAYWRIGHT_URL ?? 'http://localhost:3000',
34+
screenshot: 'only-on-failure',
35+
trace: 'retain-on-failure',
36+
},
37+
38+
outputDir: 'node_modules/.cache/e2e-test-results',
39+
40+
projects: [
41+
{
42+
testDir: 'plugins/quay/tests',
43+
use: {
44+
channel: 'chrome',
45+
},
46+
},
47+
],
48+
});

workspaces/quay/plugins/quay/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@
5757
"react-router-dom": "^6.0.0"
5858
},
5959
"devDependencies": {
60+
"@axe-core/playwright": "^4.11.0",
6061
"@backstage/cli": "^0.34.4",
6162
"@backstage/core-app-api": "^1.19.1",
6263
"@backstage/dev-utils": "^1.1.15",
6364
"@backstage/test-utils": "^1.7.12",
6465
"@backstage/ui": "^0.8.2",
65-
"@playwright/test": "^1.52.0",
66+
"@playwright/test": "^1.56.1",
6667
"@testing-library/jest-dom": "^6.0.0",
6768
"@testing-library/react": "^15.0.0",
6869
"@types/react": "^18.2.58",

workspaces/quay/plugins/quay/tests/quay.spec.ts

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,13 @@ test.describe('Quay plugin', () => {
2525
const context = await browser.newContext();
2626
page = await context.newPage();
2727
common = new Common(page);
28-
2928
await common.loginAsGuest();
3029
await expect(
3130
page.getByRole('link', { name: 'backstage-test/test-images' }),
32-
).toBeEnabled({ timeout: 20000 });
33-
});
34-
35-
test.afterAll(async ({ browser }) => {
36-
await browser.close();
31+
).toBeEnabled();
3732
});
3833

39-
test('All columns are shown in the table', async () => {
34+
test('All necessary elements are visible', async ({ browser }, testInfo) => {
4035
const columns = [
4136
'Tag',
4237
'Last Modified',
@@ -45,11 +40,17 @@ test.describe('Quay plugin', () => {
4540
'Expires',
4641
'Manifest',
4742
];
48-
const thead = page.locator('thead');
43+
const table = page.getByTestId('quay-repo-table');
4944

50-
for (const col of columns) {
51-
await expect(thead.getByText(col)).toBeVisible();
45+
await expect(table).toBeVisible();
46+
await expect(table.getByPlaceholder('Filter')).toBeVisible();
47+
48+
for (const column of columns) {
49+
await expect(
50+
table.getByRole('columnheader', { name: column }),
51+
).toBeVisible();
5252
}
53+
await common.a11yCheck(testInfo);
5354
});
5455

5556
test('Vulnerabilities are listed', async () => {
@@ -62,36 +63,78 @@ test.describe('Quay plugin', () => {
6263
}
6364
});
6465

65-
test('Vulnerability details are accessible', async () => {
66-
await page.getByRole('link', { name: 'High' }).first().click();
67-
await expect(page.getByText('Vulnerabilities for')).toBeVisible({
68-
timeout: 15000,
69-
});
66+
test('Different scan results exist', async () => {
67+
await expect(page.getByRole('link', { name: 'Passed' })).toBeVisible();
68+
await expect(page.getByText('Queued')).toBeVisible();
69+
await expect(page.getByText('Unsupported')).toBeVisible();
7070
});
7171

72-
test('Vulnerability columns are shown', async () => {
73-
const columns = [
74-
'Advisory',
75-
'Severity',
76-
'Package Name',
77-
'Current Version',
78-
'Fixed By',
79-
];
72+
test('Filtering works', async () => {
73+
const tbody = page.locator('tbody');
74+
const rows = tbody.getByRole('row').filter({ hasText: 'sha' });
8075

81-
for (const col of columns) {
82-
await expect(page.getByText(col)).toBeVisible();
83-
}
84-
});
76+
await page.getByPlaceholder('Filter').fill('v3');
77+
await expect(rows).toHaveCount(1);
78+
await expect(tbody.getByText('Passed')).toBeVisible();
8579

86-
test('Vulnerability rows are shown', async () => {
87-
const tbody = page.locator('tbody');
88-
await expect(tbody.locator('tr')).toHaveCount(5);
80+
await page.getByRole('button', { name: 'Clear Search' }).click();
81+
await expect(rows).toHaveCount(5);
8982
});
9083

91-
test('Link back to repository works', async () => {
92-
await page.getByRole('link', { name: 'Back to repository' }).click();
93-
await expect(
94-
page.getByRole('link', { name: 'backstage-test/test-images' }),
95-
).toBeEnabled();
84+
test.describe('Vulnerability details', () => {
85+
test.beforeAll(async () => {
86+
await page.getByRole('link', { name: 'High' }).first().click();
87+
});
88+
89+
test('Vulnerability details are accessible', async ({
90+
browser,
91+
}, testInfo) => {
92+
await expect(page.getByText('Vulnerabilities for')).toBeVisible();
93+
await expect(
94+
page.getByRole('heading', { name: `Vulnerabilities for` }),
95+
).toBeVisible();
96+
await expect(page.getByPlaceholder('Filter')).toBeVisible();
97+
await common.a11yCheck(testInfo);
98+
});
99+
100+
test('Vulnerability columns are shown', async () => {
101+
const columns = [
102+
'Advisory',
103+
'Severity',
104+
'Package Name',
105+
'Current Version',
106+
'Fixed By',
107+
];
108+
109+
for (const col of columns) {
110+
await expect(page.getByText(col)).toBeVisible();
111+
}
112+
});
113+
114+
test('Vulnerability rows are shown', async () => {
115+
const tbody = page.locator('tbody');
116+
await expect(tbody.getByRole('row')).toHaveCount(5);
117+
await expect(tbody.getByText('High')).toHaveCount(2);
118+
await expect(tbody.getByText('Medium')).toHaveCount(2);
119+
await expect(tbody.getByText(/^Low$/)).toHaveCount(1);
120+
});
121+
122+
test('Filtering works', async () => {
123+
const tbody = page.locator('tbody');
124+
const rows = tbody.getByRole('row').filter({ hasText: 'RHSA' });
125+
126+
await page.getByPlaceholder('Filter').fill('high');
127+
await expect(rows).toHaveCount(2);
128+
129+
await page.getByRole('button', { name: 'Clear Search' }).click();
130+
await expect(rows).toHaveCount(5);
131+
});
132+
133+
test('Link back to repository works', async () => {
134+
await page.getByRole('link', { name: 'Back to repository' }).click();
135+
await expect(
136+
page.getByRole('link', { name: 'backstage-test/test-images' }),
137+
).toBeEnabled();
138+
});
96139
});
97140
});

workspaces/quay/plugins/quay/tests/quayHelper.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { expect, type Locator, type Page } from '@playwright/test';
16+
import AxeBuilder from '@axe-core/playwright';
17+
import { expect, TestInfo, type Locator, type Page } from '@playwright/test';
1718

1819
export class Common {
1920
page: Page;
@@ -61,4 +62,16 @@ export class Common {
6162
await this.clickButton('Enter');
6263
await this.waitForSideBarVisible();
6364
}
65+
66+
async a11yCheck(testInfo: TestInfo) {
67+
const page = this.page;
68+
const accessibilityScanResults = await new AxeBuilder({ page })
69+
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
70+
.analyze();
71+
72+
await testInfo.attach('accessibility-scan-results.json', {
73+
body: JSON.stringify(accessibilityScanResults.violations, null, 2),
74+
contentType: 'application/json',
75+
});
76+
}
6477
}

workspaces/quay/yarn.lock

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,17 @@ __metadata:
10451045
languageName: node
10461046
linkType: hard
10471047

1048+
"@axe-core/playwright@npm:^4.11.0":
1049+
version: 4.11.0
1050+
resolution: "@axe-core/playwright@npm:4.11.0"
1051+
dependencies:
1052+
axe-core: "npm:~4.11.0"
1053+
peerDependencies:
1054+
playwright-core: ">= 1.0.0"
1055+
checksum: 10/54de38082deeb1d9022b47f1412ebba7c9b93aa09a0dbc5b1609d35b4f5d05f43840968aeeea913b340b4d89ab1ec0c8bb18aeb1f4c609751ae95e7b8ae2b724
1056+
languageName: node
1057+
linkType: hard
1058+
10481059
"@azure/abort-controller@npm:^2.0.0, @azure/abort-controller@npm:^2.1.2":
10491060
version: 2.1.2
10501061
resolution: "@azure/abort-controller@npm:2.1.2"
@@ -1682,6 +1693,7 @@ __metadata:
16821693
version: 0.0.0-use.local
16831694
resolution: "@backstage-community/plugin-quay@workspace:plugins/quay"
16841695
dependencies:
1696+
"@axe-core/playwright": "npm:^4.11.0"
16851697
"@backstage-community/plugin-quay-common": "workspace:^"
16861698
"@backstage/catalog-model": "npm:^1.7.5"
16871699
"@backstage/cli": "npm:^0.34.4"
@@ -1698,7 +1710,7 @@ __metadata:
16981710
"@material-ui/core": "npm:^4.12.2"
16991711
"@material-ui/icons": "npm:^4.11.3"
17001712
"@material-ui/lab": "npm:4.0.0-alpha.61"
1701-
"@playwright/test": "npm:^1.52.0"
1713+
"@playwright/test": "npm:^1.56.1"
17021714
"@testing-library/jest-dom": "npm:^6.0.0"
17031715
"@testing-library/react": "npm:^15.0.0"
17041716
"@types/react": "npm:^18.2.58"
@@ -5743,6 +5755,7 @@ __metadata:
57435755
"@backstage/repo-tools": "npm:^0.15.3"
57445756
"@changesets/cli": "npm:^2.27.1"
57455757
"@ianvs/prettier-plugin-sort-imports": "npm:^4.4.0"
5758+
"@playwright/test": "npm:^1.56.1"
57465759
knip: "npm:^5.27.4"
57475760
node-gyp: "npm:^9.0.0"
57485761
prettier: "npm:^2.3.2"
@@ -8062,14 +8075,14 @@ __metadata:
80628075
languageName: node
80638076
linkType: hard
80648077

8065-
"@playwright/test@npm:^1.52.0":
8066-
version: 1.52.0
8067-
resolution: "@playwright/test@npm:1.52.0"
8078+
"@playwright/test@npm:^1.56.1":
8079+
version: 1.57.0
8080+
resolution: "@playwright/test@npm:1.57.0"
80688081
dependencies:
8069-
playwright: "npm:1.52.0"
8082+
playwright: "npm:1.57.0"
80708083
bin:
80718084
playwright: cli.js
8072-
checksum: 10/e18a4eb626c7bc6cba212ff2e197cf9ae2e4da1c91bfdf08a744d62e27222751173e4b220fa27da72286a89a3b4dea7c09daf384d23708f284b64f98e9a63a88
8085+
checksum: 10/07f5ba4841b2db1dea70d821004c5156b692488e13523c096ce3487d30f95f34ccf30ba6467ece60c86faac27ae382213b7eacab48a695550981b2e811e5e579
80738086
languageName: node
80748087
linkType: hard
80758088

@@ -15060,10 +15073,10 @@ __metadata:
1506015073
languageName: node
1506115074
linkType: hard
1506215075

15063-
"axe-core@npm:^4.10.0":
15064-
version: 4.10.2
15065-
resolution: "axe-core@npm:4.10.2"
15066-
checksum: 10/a69423b2ff16c15922c4ea7cf9cc5112728a2817bbe0f2cc212248d648885ffd1ba554e3a341dfc289cd9e67fc0d06f333b5c6837c5c38ca6652507381216fc1
15076+
"axe-core@npm:^4.10.0, axe-core@npm:~4.11.0":
15077+
version: 4.11.0
15078+
resolution: "axe-core@npm:4.11.0"
15079+
checksum: 10/18254ee95bc328aec9a909b22e4b22e8ff14a21363fdbd1a5227267e66bf1d2fc1425c186e9001759aab5827cf4ee9dc30f7ea57e8200cbf7a1cd555ed21a908
1506715080
languageName: node
1506815081
linkType: hard
1506915082

@@ -27323,27 +27336,27 @@ __metadata:
2732327336
languageName: node
2732427337
linkType: hard
2732527338

27326-
"playwright-core@npm:1.52.0":
27327-
version: 1.52.0
27328-
resolution: "playwright-core@npm:1.52.0"
27339+
"playwright-core@npm:1.57.0":
27340+
version: 1.57.0
27341+
resolution: "playwright-core@npm:1.57.0"
2732927342
bin:
2733027343
playwright-core: cli.js
27331-
checksum: 10/42e13f5f98dc25ebc95525fb338a215b9097b2ba39d41e99972a190bf75d79979f163f5bc07b1ca06847ee07acb2c9b487d070fab67e9cd55e33310fc05aca3c
27344+
checksum: 10/ec066602f0196f036006caee14a30d0a57533a76673bb9a0c609ef56e21decf018f0e8d402ba2fb18251393be6a1c9e193c83266f1670fe50838c5340e220de0
2733227345
languageName: node
2733327346
linkType: hard
2733427347

27335-
"playwright@npm:1.52.0":
27336-
version: 1.52.0
27337-
resolution: "playwright@npm:1.52.0"
27348+
"playwright@npm:1.57.0":
27349+
version: 1.57.0
27350+
resolution: "playwright@npm:1.57.0"
2733827351
dependencies:
2733927352
fsevents: "npm:2.3.2"
27340-
playwright-core: "npm:1.52.0"
27353+
playwright-core: "npm:1.57.0"
2734127354
dependenciesMeta:
2734227355
fsevents:
2734327356
optional: true
2734427357
bin:
2734527358
playwright: cli.js
27346-
checksum: 10/214175446089000c2ac997b925063b95f7d86d129c5d7c74caa5ddcb05bcad598dfd569d2133a10dc82d288bf67e7858877dcd099274b0b928b9c63db7d6ecec
27359+
checksum: 10/241559210f98ef11b6bd6413f2d29da7ef67c7865b72053192f0d164fab9e0d3bd47913b3351d5de6433a8aff2d8424d4b8bd668df420bf4dda7ae9fcd37b942
2734727360
languageName: node
2734827361
linkType: hard
2734927362

0 commit comments

Comments
 (0)