Skip to content

Commit 945f62c

Browse files
committed
Add tests and 404 html page
1 parent f4aedc7 commit 945f62c

10 files changed

+471
-94
lines changed

e2e/tests/404.spec.ts

Lines changed: 127 additions & 3 deletions
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,134 @@ 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();
51+
});
52+
}
53+
});
54+
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+
// Show similar features container
90+
const similarContainer = page.locator('.similar-features-container');
91+
await expect(similarContainer).toBeVisible();
92+
await expect(page.locator('.feature-list li')).toHaveCount(
93+
data.data.length,
94+
);
95+
96+
// ✅ Click first similar feature
97+
const firstFeature = data.data[0];
98+
const firstFeatureLink = page.locator('.feature-list li a').first();
99+
await expect(firstFeatureLink).toHaveText(firstFeature.name);
100+
101+
// Click and wait for navigation
102+
await Promise.all([page.waitForNavigation(), firstFeatureLink.click()]);
103+
104+
await expect(page).toHaveURL(
105+
`${BASE_URL}/features/${firstFeature.feature_id}`,
106+
);
107+
108+
// Go back to error page to test second part
109+
await page.goBack();
110+
111+
// ✅ Click "Search for more similar features" button
112+
const searchButton = page.locator('#error-action-search-btn');
113+
await expect(searchButton).toBeVisible();
114+
115+
await Promise.all([page.waitForNavigation(), searchButton.click()]);
116+
117+
await expect(page).toHaveURL(`${BASE_URL}?q=${query}`);
118+
} else {
119+
// No similar features found
120+
await expect(
121+
page.locator('.similar-features-container'),
122+
).not.toBeVisible();
123+
await expect(page.locator('.error-message')).toContainText(
124+
'No similar features found.',
125+
);
126+
}
38127
});
39128
}
40129
});
41130

131+
test('should allow navigation from 404 page', async ({page}) => {
132+
const badUrl = `${BASE_URL}/feature/doesNotExist123`;
133+
await page.goto(badUrl);
134+
await expect(page).toHaveURL(badUrl);
135+
136+
// Home button navigation
137+
const homeButton = page.locator('#error-action-home-btn');
138+
await expect(homeButton).toBeVisible();
139+
await homeButton.click();
140+
await expect(page).toHaveURL(BASE_URL);
141+
142+
await page.goBack();
143+
144+
// Report an issue button should be present
145+
const reportButton = page.locator('#error-action-report');
146+
await expect(reportButton).toBeVisible();
147+
await expect(reportButton).toHaveAttribute(
148+
'href',
149+
'https://github.com/GoogleChrome/webstatus.dev/issues/new/choose',
150+
);
151+
});
152+
42153
test('matches the screenshot', async ({page}) => {
43-
await page.goto('http://localhost:5555/bad_url');
154+
await page.goto(`${BASE_URL}/bad_url`);
44155
const pageContainer = page.locator('.page-container');
45156
await expect(pageContainer).toHaveScreenshot('not-found-error-page.png');
46157
});
158+
159+
test('matches the screenshot with similar results', async ({page}) => {
160+
await page.goto(`${BASE_URL}/features/g`);
161+
162+
// ✅ Wait for similar results to appear
163+
const similarContainer = page.locator('.similar-features-container');
164+
await expect(similarContainer).toBeVisible({timeout: 5000});
165+
166+
const pageContainer = page.locator('.page-container');
167+
await expect(pageContainer).toHaveScreenshot(
168+
'not-found-error-page-similar-results.png',
169+
);
170+
});
Loading
Loading
Loading
Loading
Loading
Loading
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+
import {Task} from '@lit/task';
21+
22+
const GITHUB_REPO_ISSUE_LINK = 'https://github.com/example/repo/issues';
23+
24+
describe('webstatus-not-found-error-page', () => {
25+
it('renders the correct error message when featureId is missing', async () => {
26+
const component = await fixture<WebstatusNotFoundErrorPage>(
27+
html`<webstatus-not-found-error-page></webstatus-not-found-error-page>`,
28+
);
29+
30+
expect(
31+
component.shadowRoot
32+
?.querySelector('#error-status-code')
33+
?.textContent?.trim(),
34+
).to.equal('404');
35+
36+
expect(
37+
component.shadowRoot
38+
?.querySelector('#error-headline')
39+
?.textContent?.trim(),
40+
).to.equal('Page not found');
41+
42+
expect(
43+
component.shadowRoot
44+
?.querySelector('#error-detailed-message .error-message')
45+
?.textContent?.trim(),
46+
).to.equal("We couldn't find the page you're looking for.");
47+
});
48+
49+
it('renders the correct error message when featureId is provided', async () => {
50+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
51+
<webstatus-not-found-error-page
52+
.location=${{search: '?q=test-feature'}}
53+
></webstatus-not-found-error-page>
54+
`);
55+
56+
expect(
57+
component.shadowRoot?.querySelector('#error-detailed-message')
58+
?.textContent,
59+
).to.include('We could not find Feature ID: test-feature');
60+
});
61+
62+
it('displays "Loading similar features..." when the API request is pending', async () => {
63+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
64+
<webstatus-not-found-error-page
65+
.location=${{search: '?q=test-feature'}}
66+
></webstatus-not-found-error-page>
67+
`);
68+
69+
// Override the _loadingSimilarResults with a fake pending Task
70+
component._loadingSimilarResults = new Task(component, {
71+
args: () => [], // no-op args
72+
task: async () => {
73+
return new Promise(() => {}); // never resolves = stays pending
74+
},
75+
});
76+
77+
// Trigger the task manually
78+
component._loadingSimilarResults.run();
79+
await component.updateComplete;
80+
81+
const loadingMessage =
82+
component.shadowRoot?.querySelector('.loading-message');
83+
expect(loadingMessage).to.exist;
84+
expect(loadingMessage?.textContent?.trim()).to.equal(
85+
'Loading similar features...',
86+
);
87+
});
88+
89+
it('renders similar features when API returns results', async () => {
90+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
91+
<webstatus-not-found-error-page
92+
.location=${{search: '?q=g'}}
93+
></webstatus-not-found-error-page>
94+
`);
95+
96+
component.similarFeatures = [
97+
{name: 'Feature One', url: '/features/dignissimos44'},
98+
{name: 'Feature Two', url: '/features/fugiat37'},
99+
];
100+
await component.updateComplete;
101+
102+
const featureList =
103+
component.shadowRoot?.querySelectorAll('.feature-list li');
104+
expect(featureList?.length).to.equal(2);
105+
expect(featureList?.[0]?.textContent?.trim()).to.equal('Feature One');
106+
expect(featureList?.[1]?.textContent?.trim()).to.equal('Feature Two');
107+
});
108+
109+
it('renders "No similar features found." when API returns no results', async () => {
110+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
111+
<webstatus-not-found-error-page
112+
.location=${{search: '?q=test-feature'}}
113+
></webstatus-not-found-error-page>
114+
`);
115+
116+
component.similarFeatures = [];
117+
await component.updateComplete;
118+
119+
const noResultsMessage = component.shadowRoot?.querySelector(
120+
'.similar-features-container p',
121+
);
122+
expect(noResultsMessage).to.exist;
123+
expect(noResultsMessage?.textContent?.trim()).to.equal(
124+
'No similar features found.',
125+
);
126+
});
127+
128+
it('renders all three buttons when featureId and similar results exist', async () => {
129+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
130+
<webstatus-not-found-error-page
131+
.location=${{search: '?q=g'}}
132+
></webstatus-not-found-error-page>
133+
`);
134+
135+
expect(component.shadowRoot?.querySelector('#error-action-search-btn')).to
136+
.exist;
137+
expect(component.shadowRoot?.querySelector('#error-action-home-btn')).to
138+
.exist;
139+
expect(component.shadowRoot?.querySelector('#error-action-report')).to
140+
.exist;
141+
});
142+
143+
it('renders only two buttons when featureId does not exist', async () => {
144+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
145+
<webstatus-not-found-error-page
146+
.location=${{search: ''}}
147+
></webstatus-not-found-error-page>
148+
`);
149+
150+
expect(component.shadowRoot?.querySelector('#error-action-search-btn')).to
151+
.not.exist;
152+
expect(component.shadowRoot?.querySelector('#error-action-home-btn')).to
153+
.exist;
154+
expect(component.shadowRoot?.querySelector('#error-action-report')).to
155+
.exist;
156+
});
157+
158+
it('search button contains the correct query parameter', async () => {
159+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
160+
<webstatus-not-found-error-page
161+
.location=${{search: '?q=correct-query'}}
162+
></webstatus-not-found-error-page>
163+
`);
164+
165+
const searchButton = component.shadowRoot?.querySelector(
166+
'#error-action-search-btn',
167+
);
168+
expect(searchButton?.getAttribute('href')).to.equal('/?q=correct-query');
169+
});
170+
171+
it('report issue button links to GitHub', async () => {
172+
const component = await fixture<WebstatusNotFoundErrorPage>(html`
173+
<webstatus-not-found-error-page></webstatus-not-found-error-page>
174+
`);
175+
176+
const reportButton = component.shadowRoot?.querySelector(
177+
'#error-action-report',
178+
);
179+
expect(reportButton?.getAttribute('href')).to.equal(GITHUB_REPO_ISSUE_LINK);
180+
});
181+
});

0 commit comments

Comments
 (0)