Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions workspaces/quay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@backstage/repo-tools": "^0.15.3",
"@changesets/cli": "^2.27.1",
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@playwright/test": "^1.56.1",
"knip": "^5.27.4",
"node-gyp": "^9.0.0",
"prettier": "^2.3.2",
Expand Down
48 changes: 48 additions & 0 deletions workspaces/quay/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@playwright/test';

export default defineConfig({
webServer: process.env.PLAYWRIGHT_URL
? []
: {
command: 'yarn start',
cwd: 'plugins/quay',
port: 3000,
reuseExistingServer: true,
},

retries: process.env.CI ? 2 : 0,

reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]],

use: {
baseURL: process.env.PLAYWRIGHT_URL ?? 'http://localhost:3000',
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},

outputDir: 'node_modules/.cache/e2e-test-results',

projects: [
{
testDir: 'plugins/quay/tests',
use: {
channel: 'chrome',
},
},
],
});
3 changes: 2 additions & 1 deletion workspaces/quay/plugins/quay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@
"react-router-dom": "^6.0.0"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.0",
"@backstage/cli": "^0.34.4",
"@backstage/core-app-api": "^1.19.1",
"@backstage/dev-utils": "^1.1.15",
"@backstage/test-utils": "^1.7.12",
"@backstage/ui": "^0.8.2",
"@playwright/test": "^1.52.0",
"@playwright/test": "^1.56.1",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.0.0",
"@types/react": "^18.2.58",
Expand Down
113 changes: 78 additions & 35 deletions workspaces/quay/plugins/quay/tests/quay.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,13 @@ test.describe('Quay plugin', () => {
const context = await browser.newContext();
page = await context.newPage();
common = new Common(page);

await common.loginAsGuest();
await expect(
page.getByRole('link', { name: 'backstage-test/test-images' }),
).toBeEnabled({ timeout: 20000 });
});

test.afterAll(async ({ browser }) => {
await browser.close();
).toBeEnabled();
});

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

for (const col of columns) {
await expect(thead.getByText(col)).toBeVisible();
await expect(table).toBeVisible();
await expect(table.getByPlaceholder('Filter')).toBeVisible();

for (const column of columns) {
await expect(
table.getByRole('columnheader', { name: column }),
).toBeVisible();
}
await common.a11yCheck(testInfo);
});

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

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

test('Vulnerability columns are shown', async () => {
const columns = [
'Advisory',
'Severity',
'Package Name',
'Current Version',
'Fixed By',
];
test('Filtering works', async () => {
const tbody = page.locator('tbody');
const rows = tbody.getByRole('row').filter({ hasText: 'sha' });

for (const col of columns) {
await expect(page.getByText(col)).toBeVisible();
}
});
await page.getByPlaceholder('Filter').fill('v3');
await expect(rows).toHaveCount(1);
await expect(tbody.getByText('Passed')).toBeVisible();

test('Vulnerability rows are shown', async () => {
const tbody = page.locator('tbody');
await expect(tbody.locator('tr')).toHaveCount(5);
await page.getByRole('button', { name: 'Clear Search' }).click();
await expect(rows).toHaveCount(5);
});

test('Link back to repository works', async () => {
await page.getByRole('link', { name: 'Back to repository' }).click();
await expect(
page.getByRole('link', { name: 'backstage-test/test-images' }),
).toBeEnabled();
test.describe('Vulnerability details', () => {
test.beforeAll(async () => {
await page.getByRole('link', { name: 'High' }).first().click();
});

test('Vulnerability details are accessible', async ({
browser,
}, testInfo) => {
await expect(page.getByText('Vulnerabilities for')).toBeVisible();
await expect(
page.getByRole('heading', { name: `Vulnerabilities for` }),
).toBeVisible();
await expect(page.getByPlaceholder('Filter')).toBeVisible();
await common.a11yCheck(testInfo);
});

test('Vulnerability columns are shown', async () => {
const columns = [
'Advisory',
'Severity',
'Package Name',
'Current Version',
'Fixed By',
];

for (const col of columns) {
await expect(page.getByText(col)).toBeVisible();
}
});

test('Vulnerability rows are shown', async () => {
const tbody = page.locator('tbody');
await expect(tbody.getByRole('row')).toHaveCount(5);
await expect(tbody.getByText('High')).toHaveCount(2);
await expect(tbody.getByText('Medium')).toHaveCount(2);
await expect(tbody.getByText(/^Low$/)).toHaveCount(1);
});

test('Filtering works', async () => {
const tbody = page.locator('tbody');
const rows = tbody.getByRole('row').filter({ hasText: 'RHSA' });

await page.getByPlaceholder('Filter').fill('high');
await expect(rows).toHaveCount(2);

await page.getByRole('button', { name: 'Clear Search' }).click();
await expect(rows).toHaveCount(5);
});

test('Link back to repository works', async () => {
await page.getByRole('link', { name: 'Back to repository' }).click();
await expect(
page.getByRole('link', { name: 'backstage-test/test-images' }),
).toBeEnabled();
});
});
});
15 changes: 14 additions & 1 deletion workspaces/quay/plugins/quay/tests/quayHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { expect, type Locator, type Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import { expect, TestInfo, type Locator, type Page } from '@playwright/test';

export class Common {
page: Page;
Expand Down Expand Up @@ -61,4 +62,16 @@ export class Common {
await this.clickButton('Enter');
await this.waitForSideBarVisible();
}

async a11yCheck(testInfo: TestInfo) {
const page = this.page;
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();

await testInfo.attach('accessibility-scan-results.json', {
body: JSON.stringify(accessibilityScanResults.violations, null, 2),
contentType: 'application/json',
});
}
}
51 changes: 32 additions & 19 deletions workspaces/quay/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,17 @@ __metadata:
languageName: node
linkType: hard

"@axe-core/playwright@npm:^4.11.0":
version: 4.11.0
resolution: "@axe-core/playwright@npm:4.11.0"
dependencies:
axe-core: "npm:~4.11.0"
peerDependencies:
playwright-core: ">= 1.0.0"
checksum: 10/54de38082deeb1d9022b47f1412ebba7c9b93aa09a0dbc5b1609d35b4f5d05f43840968aeeea913b340b4d89ab1ec0c8bb18aeb1f4c609751ae95e7b8ae2b724
languageName: node
linkType: hard

"@azure/abort-controller@npm:^2.0.0, @azure/abort-controller@npm:^2.1.2":
version: 2.1.2
resolution: "@azure/abort-controller@npm:2.1.2"
Expand Down Expand Up @@ -1682,6 +1693,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@backstage-community/plugin-quay@workspace:plugins/quay"
dependencies:
"@axe-core/playwright": "npm:^4.11.0"
"@backstage-community/plugin-quay-common": "workspace:^"
"@backstage/catalog-model": "npm:^1.7.5"
"@backstage/cli": "npm:^0.34.4"
Expand All @@ -1698,7 +1710,7 @@ __metadata:
"@material-ui/core": "npm:^4.12.2"
"@material-ui/icons": "npm:^4.11.3"
"@material-ui/lab": "npm:4.0.0-alpha.61"
"@playwright/test": "npm:^1.52.0"
"@playwright/test": "npm:^1.56.1"
"@testing-library/jest-dom": "npm:^6.0.0"
"@testing-library/react": "npm:^15.0.0"
"@types/react": "npm:^18.2.58"
Expand Down Expand Up @@ -5743,6 +5755,7 @@ __metadata:
"@backstage/repo-tools": "npm:^0.15.3"
"@changesets/cli": "npm:^2.27.1"
"@ianvs/prettier-plugin-sort-imports": "npm:^4.4.0"
"@playwright/test": "npm:^1.56.1"
knip: "npm:^5.27.4"
node-gyp: "npm:^9.0.0"
prettier: "npm:^2.3.2"
Expand Down Expand Up @@ -8062,14 +8075,14 @@ __metadata:
languageName: node
linkType: hard

"@playwright/test@npm:^1.52.0":
version: 1.52.0
resolution: "@playwright/test@npm:1.52.0"
"@playwright/test@npm:^1.56.1":
version: 1.57.0
resolution: "@playwright/test@npm:1.57.0"
dependencies:
playwright: "npm:1.52.0"
playwright: "npm:1.57.0"
bin:
playwright: cli.js
checksum: 10/e18a4eb626c7bc6cba212ff2e197cf9ae2e4da1c91bfdf08a744d62e27222751173e4b220fa27da72286a89a3b4dea7c09daf384d23708f284b64f98e9a63a88
checksum: 10/07f5ba4841b2db1dea70d821004c5156b692488e13523c096ce3487d30f95f34ccf30ba6467ece60c86faac27ae382213b7eacab48a695550981b2e811e5e579
languageName: node
linkType: hard

Expand Down Expand Up @@ -15060,10 +15073,10 @@ __metadata:
languageName: node
linkType: hard

"axe-core@npm:^4.10.0":
version: 4.10.2
resolution: "axe-core@npm:4.10.2"
checksum: 10/a69423b2ff16c15922c4ea7cf9cc5112728a2817bbe0f2cc212248d648885ffd1ba554e3a341dfc289cd9e67fc0d06f333b5c6837c5c38ca6652507381216fc1
"axe-core@npm:^4.10.0, axe-core@npm:~4.11.0":
version: 4.11.0
resolution: "axe-core@npm:4.11.0"
checksum: 10/18254ee95bc328aec9a909b22e4b22e8ff14a21363fdbd1a5227267e66bf1d2fc1425c186e9001759aab5827cf4ee9dc30f7ea57e8200cbf7a1cd555ed21a908
languageName: node
linkType: hard

Expand Down Expand Up @@ -27323,27 +27336,27 @@ __metadata:
languageName: node
linkType: hard

"playwright-core@npm:1.52.0":
version: 1.52.0
resolution: "playwright-core@npm:1.52.0"
"playwright-core@npm:1.57.0":
version: 1.57.0
resolution: "playwright-core@npm:1.57.0"
bin:
playwright-core: cli.js
checksum: 10/42e13f5f98dc25ebc95525fb338a215b9097b2ba39d41e99972a190bf75d79979f163f5bc07b1ca06847ee07acb2c9b487d070fab67e9cd55e33310fc05aca3c
checksum: 10/ec066602f0196f036006caee14a30d0a57533a76673bb9a0c609ef56e21decf018f0e8d402ba2fb18251393be6a1c9e193c83266f1670fe50838c5340e220de0
languageName: node
linkType: hard

"playwright@npm:1.52.0":
version: 1.52.0
resolution: "playwright@npm:1.52.0"
"playwright@npm:1.57.0":
version: 1.57.0
resolution: "playwright@npm:1.57.0"
dependencies:
fsevents: "npm:2.3.2"
playwright-core: "npm:1.52.0"
playwright-core: "npm:1.57.0"
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 10/214175446089000c2ac997b925063b95f7d86d129c5d7c74caa5ddcb05bcad598dfd569d2133a10dc82d288bf67e7858877dcd099274b0b928b9c63db7d6ecec
checksum: 10/241559210f98ef11b6bd6413f2d29da7ef67c7865b72053192f0d164fab9e0d3bd47913b3351d5de6433a8aff2d8424d4b8bd668df420bf4dda7ae9fcd37b942
languageName: node
linkType: hard

Expand Down