Skip to content

Commit aeab3ab

Browse files
committed
Draft commit
1 parent f4aedc7 commit aeab3ab

File tree

4 files changed

+453
-94
lines changed

4 files changed

+453
-94
lines changed

e2e/tests/404.spec.ts

+102-3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
import {test, expect} from '@playwright/test';
1818

19+
const BASE_URL = 'http://localhost:5555';
20+
1921
test('Bad URL redirection to 404 page', async ({page}) => {
2022
const badUrls = [
2123
// Test for bad public asset
22-
'http://localhost:5555/public/junk',
24+
`${BASE_URL}/public/junk`,
2325
// Test for bad URL goes to the not found component
24-
'http://localhost:5555/bad_url',
26+
`${BASE_URL}/bad_url`,
2527
// TODO. Test for bad app urls (e.g. bad feature id)
2628
];
2729

@@ -35,12 +37,109 @@ test('Bad URL redirection to 404 page', async ({page}) => {
3537

3638
// Assert that the response status code is 404
3739
expect(response.status()).toBe(404);
40+
41+
// Check page content
42+
const errorMessage = page.locator('#error-detailed-message');
43+
await expect(errorMessage).toBeVisible();
44+
await expect(errorMessage).toContainText(
45+
"We couldn't find the page you're looking for.",
46+
);
47+
48+
// Check buttons
49+
await expect(page.locator('#error-action-home-btn')).toBeVisible();
50+
await expect(page.locator('#error-action-report')).toBeVisible();
3851
});
3952
}
4053
});
4154

55+
test('should fetch similar features from API and show results', async ({
56+
page,
57+
}) => {
58+
// Test URLs for different feature IDs
59+
const badUrls = [
60+
{badUrl: `${BASE_URL}/features/badID_1234`, query: 'badID_1234'},
61+
{badUrl: `${BASE_URL}/features/g`, query: 'g'},
62+
];
63+
const API_BASE_URL =
64+
'http://localhost:8080/v1/features?q={query}&page_size=5';
65+
66+
for (const {badUrl, query} of badUrls) {
67+
await test.step(`Testing API response for: ${badUrl}`, async () => {
68+
await page.goto(badUrl);
69+
await expect(page).toHaveURL(
70+
'http://localhost:5555/errors-404/feature-not-found?q=' + query,
71+
);
72+
73+
const featurePageResponse = await page
74+
.context()
75+
.request.fetch(page.url());
76+
77+
// Assert that the response status code is 404
78+
expect(featurePageResponse.status()).toBe(404);
79+
80+
// Mock API response for similar features
81+
const apiUrl = API_BASE_URL.replace('{query}', query);
82+
const response = await page.context().request.get(apiUrl);
83+
expect(response.status()).toBe(200);
84+
85+
const data = await response.json();
86+
const hasResults = Array.isArray(data?.data) && data.data.length > 0;
87+
88+
if (hasResults) {
89+
// Check "Here are some similar features" text
90+
await expect(page.locator('.similar-features-container')).toBeVisible();
91+
await expect(page.locator('.feature-list li')).toHaveCount(
92+
data.data.length,
93+
);
94+
95+
// Verify search button appears
96+
const searchButton = page.locator('#error-action-search-btn');
97+
await expect(searchButton).toBeVisible();
98+
await searchButton.click();
99+
await expect(page).toHaveURL(`${BASE_URL}?q=${query}`);
100+
} else {
101+
// No similar features found
102+
await expect(
103+
page.locator('.similar-features-container'),
104+
).not.toBeVisible();
105+
await expect(page.locator('p')).toContainText(
106+
'No similar features found.',
107+
);
108+
}
109+
});
110+
}
111+
});
112+
113+
test('should allow navigation from 404 page', async ({page}) => {
114+
const badUrl = `${BASE_URL}/feature/doesNotExist123`;
115+
await page.goto(badUrl);
116+
await expect(page).toHaveURL(badUrl);
117+
118+
// Home button navigation
119+
const homeButton = page.locator('#error-action-home-btn');
120+
await expect(homeButton).toBeVisible();
121+
await homeButton.click();
122+
await expect(page).toHaveURL(BASE_URL);
123+
124+
// Report an issue button should be present
125+
const reportButton = page.locator('#error-action-report');
126+
await expect(reportButton).toBeVisible();
127+
await expect(reportButton).toHaveAttribute(
128+
'href',
129+
'https://github.com/GoogleChrome/webstatus.dev/issues/new/choose',
130+
);
131+
});
132+
42133
test('matches the screenshot', async ({page}) => {
43-
await page.goto('http://localhost:5555/bad_url');
134+
await page.goto(`${BASE_URL}/bad_url`);
44135
const pageContainer = page.locator('.page-container');
45136
await expect(pageContainer).toHaveScreenshot('not-found-error-page.png');
46137
});
138+
139+
test('matches the screenshot with similar results', async ({page}) => {
140+
await page.goto(`${BASE_URL}/features/g`);
141+
const pageContainer = page.locator('.page-container');
142+
await expect(pageContainer).toHaveScreenshot(
143+
'not-found-error-page-similar-results.png',
144+
);
145+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/**
2+
* Copyright 2025 Google LLC
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+
17+
import {expect, fixture, html} from '@open-wc/testing';
18+
import '../webstatus-not-found-error-page.js';
19+
import {WebstatusNotFoundErrorPage} from '../webstatus-notfound-error-page.js';
20+
21+
const GITHUB_REPO_ISSUE_LINK = 'https://github.com/example/repo/issues';
22+
23+
describe('webstatus-not-found-error-page', () => {
24+
it('renders the correct error message when featureId is missing', async () => {
25+
const component = await fixture<WebstatusNotFoundErrorPage>(
26+
html`<webstatus-not-found-error-page></webstatus-not-found-error-page>`,
27+
);
28+
29+
expect(
30+
component.shadowRoot
31+
?.querySelector('#error-status-code')
32+
?.textContent?.trim(),
33+
).to.equal('404');
34+
35+
expect(
36+
component.shadowRoot
37+
?.querySelector('#error-headline')
38+
?.textContent?.trim(),
39+
).to.equal('Page not found');
40+
41+
expect(
42+
component.shadowRoot
43+
?.querySelector('#error-detailed-message .error-message')
44+
?.textContent?.trim(),
45+
).to.equal("We couldn't find the page you're looking for.");
46+
});
47+
48+
it('renders the correct error message when featureId is provided', async () => {
49+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
50+
<webstatus-not-found-error-page
51+
.location=${{search: '?q=test-feature'}}
52+
></webstatus-not-found-error-page>
53+
`);
54+
55+
expect(
56+
component.shadowRoot?.querySelector('#error-detailed-message')
57+
?.textContent,
58+
).to.include('We could not find Feature ID: test-feature');
59+
});
60+
61+
it('displays "Loading similar features..." when the API request is pending', async () => {
62+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
63+
<webstatus-not-found-error-page
64+
.location=${{search: '?q=test-feature'}}
65+
></webstatus-not-found-error-page>
66+
`);
67+
68+
component._loadingSimilarResults = {status: 'pending'} as any;
69+
await component.updateComplete;
70+
71+
const loadingMessage =
72+
component.shadowRoot?.querySelector('.loading-message');
73+
expect(loadingMessage).to.exist;
74+
expect(loadingMessage?.textContent?.trim()).to.equal(
75+
'Loading similar features...',
76+
);
77+
});
78+
79+
it('renders similar features when API returns results', async () => {
80+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
81+
<webstatus-not-found-error-page
82+
.location=${{search: '?q=test-feature'}}
83+
></webstatus-not-found-error-page>
84+
`);
85+
86+
component.similarFeatures = [
87+
{name: 'Feature One', url: '/features/one'},
88+
{name: 'Feature Two', url: '/features/two'},
89+
];
90+
await component.updateComplete;
91+
92+
const featureList =
93+
component.shadowRoot?.querySelectorAll('.feature-list li');
94+
expect(featureList?.length).to.equal(2);
95+
expect(featureList?.[0]?.textContent?.trim()).to.equal('Feature One');
96+
expect(featureList?.[1]?.textContent?.trim()).to.equal('Feature Two');
97+
});
98+
99+
it('renders "No similar features found." when API returns no results', async () => {
100+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
101+
<webstatus-not-found-error-page
102+
.location=${{search: '?q=test-feature'}}
103+
></webstatus-not-found-error-page>
104+
`);
105+
106+
component.similarFeatures = [];
107+
await component.updateComplete;
108+
109+
const noResultsMessage = component.shadowRoot?.querySelector(
110+
'.similar-features-container p',
111+
);
112+
expect(noResultsMessage).to.exist;
113+
expect(noResultsMessage?.textContent?.trim()).to.equal(
114+
'No similar features found.',
115+
);
116+
});
117+
118+
it('renders all three buttons when featureId exists', async () => {
119+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
120+
<webstatus-not-found-error-page
121+
.location=${{search: '?q=test-feature'}}
122+
></webstatus-not-found-error-page>
123+
`);
124+
125+
expect(component.shadowRoot?.querySelector('#error-action-search-btn')).to
126+
.exist;
127+
expect(component.shadowRoot?.querySelector('#error-action-home-btn')).to
128+
.exist;
129+
expect(component.shadowRoot?.querySelector('#error-action-report')).to
130+
.exist;
131+
});
132+
133+
it('renders only two buttons when featureId does not exist', async () => {
134+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
135+
<webstatus-not-found-error-page
136+
.location=${{search: ''}}
137+
></webstatus-not-found-error-page>
138+
`);
139+
140+
expect(component.shadowRoot?.querySelector('#error-action-search-btn')).to
141+
.not.exist;
142+
expect(component.shadowRoot?.querySelector('#error-action-home-btn')).to
143+
.exist;
144+
expect(component.shadowRoot?.querySelector('#error-action-report')).to
145+
.exist;
146+
});
147+
148+
it('search button contains the correct query parameter', async () => {
149+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
150+
<webstatus-not-found-error-page
151+
.location=${{search: '?q=correct-query'}}
152+
></webstatus-not-found-error-page>
153+
`);
154+
155+
const searchButton = component.shadowRoot?.querySelector(
156+
'#error-action-search-btn',
157+
);
158+
expect(searchButton?.getAttribute('href')).to.equal('/?q=correct-query');
159+
});
160+
161+
it('report issue button links to GitHub', async () => {
162+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
163+
<webstatus-not-found-error-page></webstatus-not-found-error-page>
164+
`);
165+
166+
const reportButton = component.shadowRoot?.querySelector(
167+
'#error-action-report',
168+
);
169+
expect(reportButton?.getAttribute('href')).to.equal(GITHUB_REPO_ISSUE_LINK);
170+
});
171+
172+
it('applies correct gap spacing in error-actions when featureId is present', async () => {
173+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
174+
<webstatus-not-found-error-page
175+
.location=${{search: '?q=test-feature'}}
176+
></webstatus-not-found-error-page>
177+
`);
178+
179+
const errorActions = component.shadowRoot?.querySelector('#error-actions');
180+
expect(errorActions?.getAttribute('style')).to.include('gap: 32px');
181+
});
182+
183+
it('applies correct gap spacing in error-actions when featureId is absent', async () => {
184+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
185+
<webstatus-not-found-error-page
186+
.location=${{search: ''}}
187+
></webstatus-not-found-error-page>
188+
`);
189+
190+
const errorActions = component.shadowRoot?.querySelector('#error-actions');
191+
expect(errorActions?.getAttribute('style')).to.include('gap: 16px');
192+
});
193+
});

0 commit comments

Comments
 (0)