Skip to content

Commit 889d738

Browse files
committed
fix(e2e): add grammar page tests and update CI config
- Implement comprehensive end-to-end tests for the grammar page verifying redirects, content rendering, navigation, and link behavior. - Add grammar page-specific test suite to `test:ci` script in `package.json`.
1 parent ea697a1 commit 889d738

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
"generate-redirects": "node scripts/generate-redirect-pages.js",
139139
"lint": "next lint",
140140
"test": "playwright test test",
141-
"test:ci": "playwright test test/production",
141+
"test:ci": "playwright test test/production test/e2e/grammar/",
142142
"test:e2e": "playwright test test/e2e",
143143
"test:e2e:ci": "CI=true playwright test test/e2e",
144144
"test:e2e:headed": "playwright test test/e2e --headed",

test/e2e/grammar/grammar.spec.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { expect, test } from '@playwright/test';
2+
import { GrammarPage } from './page';
3+
import { checkScreenshot } from '../../utils';
4+
import { MICRO_ANIMATION_TIMEOUT_LONG } from '../visual-constants';
5+
6+
test.describe('Grammar: support', () => {
7+
test('Should redirect from .html to clean URL', async ({ page }) => {
8+
await page.goto('/docs/reference/grammar.html');
9+
10+
await expect(page).toHaveURL('/grammar/');
11+
12+
const redirected = new GrammarPage(page);
13+
await expect(redirected.layout).toBeVisible();
14+
});
15+
16+
test('Should redirect from nested URL', async ({ page }) => {
17+
await page.goto('/docs/reference/grammar/');
18+
19+
await expect(page).toHaveURL('/grammar/');
20+
21+
const redirected = new GrammarPage(page);
22+
await expect(redirected.layout).toBeVisible();
23+
});
24+
})
25+
26+
test.describe('Grammar: content', () => {
27+
let grammar: GrammarPage;
28+
29+
test.beforeEach(async ({ page }) => {
30+
grammar = new GrammarPage(page);
31+
await grammar.init();
32+
});
33+
34+
test('Should render grammar page with title', async () => {
35+
await expect(grammar.layout).toBeVisible();
36+
await expect(grammar.title).toBeVisible();
37+
});
38+
39+
test('Should have grammar items', async () => {
40+
const itemsCount = await grammar.items.count();
41+
expect(itemsCount).toBeGreaterThan(10);
42+
});
43+
44+
test('Should have declarations with valid ids', async () => {
45+
const declarationsCount = await grammar.declarations.count();
46+
expect(declarationsCount).toBeGreaterThan(10);
47+
48+
// Check that known declarations exist with correct ids
49+
await expect(grammar.getDeclarationById('kotlinFile')).toBeVisible();
50+
await expect(grammar.getDeclarationById('script')).toBeVisible();
51+
await expect(grammar.getDeclarationById('classDeclaration')).toBeVisible();
52+
});
53+
54+
test('Should navigate to declaration via direct anchor URL', async () => {
55+
await grammar.gotoHash('kotlinFile');
56+
57+
const declaration = grammar.getDeclarationById('kotlinFile');
58+
await expect(declaration).toBeInViewport();
59+
});
60+
61+
test('Should navigate when clicking identifier link', async () => {
62+
// Find and click on an identifier link
63+
const identifierLink = grammar.getIdentifierLink('shebangLine');
64+
await identifierLink.click();
65+
66+
// Wait for smooth scroll animation
67+
await grammar.page.waitForTimeout(MICRO_ANIMATION_TIMEOUT_LONG);
68+
69+
// Check URL contains the hash
70+
expect(grammar.page.url()).toContain('#shebangLine');
71+
72+
// Check the target declaration is in viewport
73+
await expect(grammar.getDeclarationById('shebangLine')).toBeInViewport();
74+
});
75+
76+
test('Should have working usages links', async () => {
77+
// Navigate to a declaration that has "used by" links
78+
await grammar.gotoHash('shebangLine');
79+
80+
// Find the "used by" section and click a link
81+
const usagesLink = grammar.layout.locator('.grammar-declaration-usedby a').first();
82+
await expect(usagesLink).toBeVisible();
83+
84+
const linkText = await usagesLink.textContent();
85+
await usagesLink.click();
86+
87+
// Wait for smooth scroll
88+
await grammar.page.waitForTimeout(MICRO_ANIMATION_TIMEOUT_LONG);
89+
90+
// Verify navigation happened
91+
expect(grammar.page.url()).toContain(`#${linkText}`);
92+
});
93+
94+
test('Should rebase relative URLs correctly', async () => {
95+
// External links should have full URL (GitHub spec repo)
96+
const externalLink = grammar.layout.locator('a[href*="github.com/Kotlin/kotlin-spec"]').first();
97+
await expect(externalLink).toBeVisible();
98+
const externalHref = await externalLink.getAttribute('href');
99+
expect(externalHref).toContain('https://github.com/Kotlin/kotlin-spec');
100+
101+
// Relative docs links should point to /docs/ path
102+
const docsLink = grammar.layout.locator('a[href*="packages.html"]').first();
103+
await expect(docsLink).toBeVisible();
104+
const docsHref = await docsLink.getAttribute('href');
105+
expect(docsHref).toMatch('/docs/packages.html');
106+
});
107+
108+
test('Should render links with Inline Attribute List (target="_blank")', async () => {
109+
// Links to external resources should have target="_blank" from IAL syntax
110+
const externalLink = grammar.layout.locator('a[href*="github.com/Kotlin/kotlin-spec"][target="_blank"]').first();
111+
await expect(externalLink).toBeVisible();
112+
});
113+
114+
test('Should apply DEFAULT_OPTIONS overrides for markdown elements', async () => {
115+
// Check h2 has typography class
116+
const h2Element = grammar.layout.locator('h2').first();
117+
await expect(h2Element).toBeVisible();
118+
const h2Class = await h2Element.getAttribute('class');
119+
expect(h2Class).toContain('rs-h2');
120+
121+
// Check h3 has typography class
122+
const h3Element = grammar.layout.locator('h3').first();
123+
await expect(h3Element).toBeVisible();
124+
const h3Class = await h3Element.getAttribute('class');
125+
expect(h3Class).toContain('rs-h3');
126+
});
127+
128+
test('Should render grammar item properly on desktop', async () => {
129+
// Find the kotlinFile grammar item (stable, unlikely to change)
130+
const grammarItem = grammar.items.filter({
131+
has: grammar.page.locator('#kotlinFile')
132+
}).first();
133+
134+
await expect(grammarItem).toBeVisible();
135+
await checkScreenshot(grammarItem);
136+
});
137+
138+
test('Should render declaration with usages properly on desktop', async () => {
139+
// shebangLine has "used by" section
140+
const declaration = grammar.declarations.filter({
141+
has: grammar.page.locator('#shebangLine')
142+
}).first();
143+
144+
await expect(declaration).toBeVisible();
145+
await checkScreenshot(declaration);
146+
});
147+
});

test/e2e/grammar/page.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Locator, Page } from '@playwright/test';
2+
import { BasePage } from '../../page/base-page';
3+
4+
export const GRAMMAR_URL = '/grammar/';
5+
6+
export class GrammarPage implements BasePage {
7+
readonly page: Page;
8+
readonly layout: Locator;
9+
readonly title: Locator;
10+
readonly items: Locator;
11+
readonly declarations: Locator;
12+
readonly identifiers: Locator;
13+
14+
constructor(page: Page) {
15+
this.page = page;
16+
this.layout = page.getByTestId('grammar-page');
17+
this.title = page.locator('h1').filter({ hasText: 'Grammar' });
18+
this.items = page.getByTestId('grammar-item');
19+
this.declarations = page.getByTestId('grammar-declaration');
20+
this.identifiers = page.getByTestId('grammar-identifier');
21+
}
22+
23+
async init() {
24+
await this.page.goto(GRAMMAR_URL);
25+
await this.layout.waitFor();
26+
}
27+
28+
async gotoHash(hash: string) {
29+
await this.page.goto(`${GRAMMAR_URL}#${hash}`);
30+
await this.layout.waitFor();
31+
}
32+
33+
getDeclarationById(name: string): Locator {
34+
return this.layout.locator(`#${name}`);
35+
}
36+
37+
getIdentifierLink(name: string): Locator {
38+
return this.identifiers.filter({ hasText: name }).first();
39+
}
40+
}

0 commit comments

Comments
 (0)