Skip to content

Commit e1d70ac

Browse files
authored
chore(e2e): e2e tests for theme plugin with accessibility tests (#1742)
* chore(e2e): e2e tests for theme plugin with accessibility tests Signed-off-by: Sanket Saikia <ssaikia@redhat.com> * chore(e2e): e2e tests for theme plugin with accessibility tests Signed-off-by: Sanket Saikia <ssaikia@redhat.com> * Added a11y checks for test pages Signed-off-by: Sanket Saikia <ssaikia@redhat.com> * dedupe Signed-off-by: Sanket Saikia <ssaikia@redhat.com> * dark mode a11y Signed-off-by: Sanket Saikia <ssaikia@redhat.com> * folder utils Signed-off-by: Sanket Saikia <ssaikia@redhat.com> --------- Signed-off-by: Sanket Saikia <ssaikia@redhat.com>
1 parent d8bb650 commit e1d70ac

10 files changed

Lines changed: 427 additions & 31 deletions

File tree

workspaces/theme/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,6 @@ site
5353

5454
# E2E test reports
5555
e2e-test-report/
56+
57+
# Screenshots
58+
screenshots/

workspaces/theme/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"directory": "workspaces/theme"
4040
},
4141
"devDependencies": {
42+
"@axe-core/playwright": "^4.11.0",
4243
"@backstage/cli": "^0.34.1",
4344
"@backstage/e2e-test-utils": "^0.1.1",
4445
"@backstage/repo-tools": "^0.15.1",

workspaces/theme/packages/app/e2e-tests/app.test.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Red Hat, Inc.
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 { test, TestInfo, expect } from '@playwright/test';
17+
import { ThemeVerifier } from './utils/theme-verifier';
18+
import { ThemeConstants } from './utils/theme-constants';
19+
import { TestUtils } from './utils/test-utils';
20+
import { runAccessibilityTests } from './utils/acessibility';
21+
22+
test.describe('CustomTheme should be applied', () => {
23+
let themeVerifier: ThemeVerifier;
24+
let testUtils: TestUtils;
25+
26+
test.beforeEach(async ({ page }) => {
27+
test.info().annotations.push({
28+
type: 'component',
29+
description: 'core',
30+
});
31+
themeVerifier = new ThemeVerifier(page);
32+
testUtils = new TestUtils(page);
33+
await testUtils.loginAsGuest();
34+
});
35+
36+
test('Verify theme colors are applied and make screenshots', async ({
37+
page,
38+
}, testInfo: TestInfo) => {
39+
const themes = ThemeConstants.getThemes();
40+
41+
await runAccessibilityTests(
42+
page,
43+
testInfo,
44+
'accessibility-scan-results.json',
45+
{ skipViolationsAssert: true },
46+
);
47+
48+
for (const theme of themes) {
49+
await themeVerifier.setTheme(theme.name);
50+
await themeVerifier.verifyHeaderGradient(
51+
`linear-gradient(90deg, ${theme.headerColor1}, ${theme.headerColor2})`,
52+
);
53+
await themeVerifier.verifyBorderLeftColor(theme.navigationIndicatorColor);
54+
await themeVerifier.takeScreenshotAndAttach(
55+
`screenshots/custom-theme-${theme.name}-inspection.png`,
56+
testInfo,
57+
`custom-theme-${theme.name}-inspection`,
58+
);
59+
await themeVerifier.verifyPrimaryColors(theme.primaryColor);
60+
}
61+
});
62+
63+
test('Verify that title for Backstage can be customized', async ({
64+
page,
65+
}) => {
66+
await expect(page).toHaveTitle(/My Company Catalog/);
67+
});
68+
69+
test('Verify accessibility of the test pages', async ({
70+
page,
71+
}, testInfo: TestInfo) => {
72+
const tabs = {
73+
'BCC tests': ['Card Example'],
74+
'BUI tests': ['Table Example', 'Card Example'],
75+
'MUI v4 tests': ['Papers', 'Tabs', 'Grids', 'Inline styles'],
76+
'MUI v5 tests': ['Papers', 'Tabs', 'Grids', 'Inline styles'],
77+
};
78+
await page.getByRole('link', { name: 'BCC tests' }).click();
79+
80+
const themeNames = ['RHDH Dark (latest)', 'RHDH Light (latest)'];
81+
for (const themeName of themeNames) {
82+
await page.getByRole('button', { name: themeName }).click();
83+
for (const [tab, subTabs] of Object.entries(tabs)) {
84+
await page.getByRole('link', { name: tab }).click();
85+
await runAccessibilityTests(
86+
page,
87+
testInfo,
88+
`${themeName}-${tab}-accessibility`,
89+
{ skipViolationsAssert: true },
90+
);
91+
for (const subTab of subTabs) {
92+
await page.getByRole('tab', { name: subTab }).click();
93+
await runAccessibilityTests(
94+
page,
95+
testInfo,
96+
`${themeName}-${tab}-${subTab}-accessibility`,
97+
{ skipViolationsAssert: true },
98+
);
99+
}
100+
}
101+
}
102+
});
103+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright Red Hat, Inc.
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 AxeBuilder from '@axe-core/playwright';
17+
import { expect, Page, TestInfo } from '@playwright/test';
18+
19+
export async function runAccessibilityTests(
20+
page: Page,
21+
testInfo: TestInfo,
22+
attachName = 'accessibility-scan-results.json',
23+
options: { skipViolationsAssert?: boolean; attachName?: string } = {
24+
skipViolationsAssert: false,
25+
},
26+
) {
27+
const accessibilityScanResults = await new AxeBuilder({ page })
28+
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
29+
.analyze();
30+
31+
await testInfo.attach(attachName, {
32+
body: JSON.stringify(accessibilityScanResults, null, 2),
33+
contentType: 'application/json',
34+
});
35+
36+
if (!options?.skipViolationsAssert) {
37+
expect(
38+
accessibilityScanResults.violations,
39+
'Accessibility violations found',
40+
).toEqual([]);
41+
}
42+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Red Hat, Inc.
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 { Page, expect } from '@playwright/test';
17+
18+
export class TestUtils {
19+
private page: Page;
20+
21+
constructor(page: Page) {
22+
this.page = page;
23+
}
24+
25+
async loginAsGuest() {
26+
await this.page.goto('/');
27+
await this.waitForLoad(240000);
28+
29+
this.page.on('dialog', async dialog => {
30+
// eslint-disable-next-line no-console
31+
console.log(`Dialog message: ${dialog.message()}`);
32+
await dialog.accept();
33+
});
34+
35+
await this.clickButton('Enter');
36+
await this.verifyHeading('My Company Catalog');
37+
}
38+
39+
async waitForLoad(timeout = 120000) {
40+
const loadingSelectors = [
41+
'[data-testid="loading"]',
42+
'.MuiCircularProgress-root',
43+
'[role="progressbar"]',
44+
];
45+
46+
for (const selector of loadingSelectors) {
47+
try {
48+
await this.page.waitForSelector(selector, {
49+
state: 'hidden',
50+
timeout: timeout,
51+
});
52+
} catch (error) {
53+
if (error instanceof Error && error.name === 'TimeoutError') {
54+
// eslint-disable-next-line no-console
55+
console.log(
56+
`Loading selector ${selector} not found or already hidden`,
57+
);
58+
} else {
59+
throw error;
60+
}
61+
}
62+
}
63+
}
64+
65+
async clickButton(buttonText: string): Promise<void> {
66+
await this.page.getByRole('button', { name: buttonText }).click();
67+
}
68+
69+
async verifyHeading(headingText: string): Promise<void> {
70+
await expect(this.page.getByText(headingText)).toBeVisible();
71+
}
72+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Red Hat, Inc.
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+
type ThemeInfo = {
17+
name:
18+
| 'RHDH Light (latest)'
19+
| 'RHDH Dark (latest)'
20+
| 'RHDH Light (customized)'
21+
| 'RHDH Dark (customized)'
22+
| 'Backstage Light'
23+
| 'Backstage Dark';
24+
primaryColor: string;
25+
headerColor1: string;
26+
headerColor2: string;
27+
navigationIndicatorColor: string;
28+
};
29+
30+
export class ThemeConstants {
31+
static getThemes() {
32+
const lightLatest: ThemeInfo = {
33+
name: 'RHDH Light (latest)',
34+
primaryColor: 'rgb(0, 102, 204)',
35+
headerColor1: 'rgb(255, 255, 255)',
36+
headerColor2: 'rgb(255, 255, 255)',
37+
navigationIndicatorColor: 'rgba(0, 0, 0, 0)',
38+
};
39+
const darkLatest: ThemeInfo = {
40+
name: 'RHDH Dark (latest)',
41+
primaryColor: 'rgb(146, 197, 249)',
42+
headerColor1: 'rgb(41, 41, 41)',
43+
headerColor2: 'rgb(41, 41, 41)',
44+
navigationIndicatorColor: 'rgba(0, 0, 0, 0)',
45+
};
46+
const lightCustomized: ThemeInfo = {
47+
name: 'RHDH Light (customized)',
48+
primaryColor: 'rgb(255, 0, 0)',
49+
headerColor1: 'rgb(255, 255, 255)',
50+
headerColor2: 'rgb(255, 255, 255)',
51+
navigationIndicatorColor: 'rgba(0, 0, 0, 0)',
52+
};
53+
const darkCustomized: ThemeInfo = {
54+
name: 'RHDH Dark (customized)',
55+
primaryColor: 'rgb(255, 0, 0)',
56+
headerColor1: 'rgb(41, 41, 41)',
57+
headerColor2: 'rgb(41, 41, 41)',
58+
navigationIndicatorColor: 'rgba(0, 0, 0, 0)',
59+
};
60+
const backstageLight: ThemeInfo = {
61+
name: 'Backstage Light',
62+
primaryColor: 'rgb(31, 84, 147)',
63+
headerColor1: 'rgb(0, 91, 75)',
64+
headerColor2: 'rgb(0, 91, 75)',
65+
navigationIndicatorColor: 'rgb(155, 240, 225)',
66+
};
67+
68+
const backstageDark: ThemeInfo = {
69+
name: 'Backstage Dark',
70+
primaryColor: 'rgb(156, 201, 255)',
71+
headerColor1: 'rgb(0, 91, 75)',
72+
headerColor2: 'rgb(0, 91, 75)',
73+
navigationIndicatorColor: 'rgb(155, 240, 225)',
74+
};
75+
76+
return [
77+
lightLatest,
78+
darkLatest,
79+
lightCustomized,
80+
darkCustomized,
81+
backstageLight,
82+
backstageDark,
83+
];
84+
}
85+
}

0 commit comments

Comments
 (0)