mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
test(e2e): migrate search-bar.ts to Playwright (#55157)
This commit is contained in:
@@ -668,7 +668,8 @@
|
||||
"see-results": "See all results for {{searchQuery}}",
|
||||
"no-tutorials": "No tutorials found",
|
||||
"try": "Looking for something? Try the search bar on this page.",
|
||||
"no-results": "We could not find anything relating to <0>{{query}}</0>"
|
||||
"no-results": "We could not find anything relating to <0>{{query}}</0>",
|
||||
"result-list": "Search results"
|
||||
},
|
||||
"misc": {
|
||||
"offline": "You appear to be offline, your progress may not be saved",
|
||||
|
||||
@@ -63,7 +63,11 @@ const CustomHits = connectHits(
|
||||
|
||||
return (
|
||||
<div className='ais-Hits'>
|
||||
<ul className='ais-Hits-list' data-cy='ais-Hits-list'>
|
||||
<ul
|
||||
className='ais-Hits-list'
|
||||
data-cy='ais-Hits-list'
|
||||
aria-label={t('search.result-list')}
|
||||
>
|
||||
{allHits.map((hit: Hit, i: number) => (
|
||||
<li
|
||||
className={
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"hits": [
|
||||
{
|
||||
"title": "Article 1",
|
||||
"objectID": "Article 1",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 1",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 2",
|
||||
"objectID": "Article 2",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 2",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 3",
|
||||
"objectID": "Article 3",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 3",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 4",
|
||||
"objectID": "Article 4",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 4",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 5",
|
||||
"objectID": "Article 5",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 5",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 6",
|
||||
"objectID": "Article 6",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 6",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 7",
|
||||
"objectID": "Article 7",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 7",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 8",
|
||||
"objectID": "Article 8",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 8",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"hits": [
|
||||
{
|
||||
"title": "Article 1",
|
||||
"objectID": "Article 1",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 1",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 2",
|
||||
"objectID": "Article 2",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 2",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 3",
|
||||
"objectID": "Article 3",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 3",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 4",
|
||||
"objectID": "Article 4",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 4",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Article 5",
|
||||
"objectID": "Article 5",
|
||||
"_highlightResult": {
|
||||
"title": {
|
||||
"value": "<ais-highlight-0000000000>Article</ais-highlight-0000000000> 5",
|
||||
"matchLevel": "full"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
import translations from '../client/i18n/locales/english/translations.json';
|
||||
import algoliaEightHits from './fixtures/algolia-eight-hits.json';
|
||||
import algoliaFiveHits from './fixtures/algolia-five-hits.json';
|
||||
|
||||
const haveApiKeys =
|
||||
process.env.ALGOLIA_APP_ID !== 'app_id_from_algolia_dashboard' &&
|
||||
process.env.ALGOLIA_API_KEY !== 'api_key_from_algolia_dashboard';
|
||||
|
||||
const getSearchInput = async ({
|
||||
page,
|
||||
isMobile
|
||||
}: {
|
||||
page: Page;
|
||||
isMobile: boolean;
|
||||
}) => {
|
||||
if (isMobile) {
|
||||
const menuButton = page.getByRole('button', {
|
||||
name: translations.buttons.menu
|
||||
});
|
||||
await expect(menuButton).toBeVisible();
|
||||
await menuButton.click();
|
||||
}
|
||||
|
||||
return page.getByLabel('Search');
|
||||
};
|
||||
|
||||
const search = async ({
|
||||
page,
|
||||
isMobile,
|
||||
query
|
||||
}: {
|
||||
page: Page;
|
||||
isMobile: boolean;
|
||||
query: string;
|
||||
}) => {
|
||||
const searchInput = await getSearchInput({ page, isMobile });
|
||||
await searchInput.fill(query);
|
||||
};
|
||||
|
||||
const mockAlgolia = async ({
|
||||
page,
|
||||
hitsPerPage
|
||||
}: {
|
||||
page: Page;
|
||||
hitsPerPage: number;
|
||||
}) => {
|
||||
if (hitsPerPage === 8) {
|
||||
await page.route(/\w+(\.algolia\.net|\.algolianet\.com)/, async route => {
|
||||
await route.fulfill({ json: algoliaEightHits });
|
||||
});
|
||||
} else if (hitsPerPage === 5) {
|
||||
await page.route(/\w+(\.algolia\.net|\.algolianet\.com)/, async route => {
|
||||
await route.fulfill({ json: algoliaFiveHits });
|
||||
});
|
||||
} else if (hitsPerPage === 0) {
|
||||
await page.route(/\w+(\.algolia\.net|\.algolianet\.com)/, async route => {
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
test.describe('Search bar', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/learn');
|
||||
|
||||
// Mock Algolia requests to prevent hitting Algolia server unnecessarily.
|
||||
// Comment out this line if you want to test against the real server.
|
||||
await mockAlgolia({ page, hitsPerPage: 8 });
|
||||
});
|
||||
|
||||
test('should display correctly', async ({ page, isMobile }) => {
|
||||
const searchInput = await getSearchInput({ page, isMobile });
|
||||
|
||||
await expect(searchInput).toBeVisible();
|
||||
await expect(searchInput).toHaveAttribute(
|
||||
'placeholder',
|
||||
translations.search.placeholder
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit search terms' })
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should return the search results when the user presses Enter', async ({
|
||||
page,
|
||||
isMobile
|
||||
}) => {
|
||||
test.skip(!haveApiKeys, 'This test requires Algolia API keys');
|
||||
|
||||
await search({ page, isMobile, query: 'article' });
|
||||
|
||||
// Wait for the search results to show up
|
||||
const resultList = page.getByRole('list', { name: 'Search results' });
|
||||
// Initially, the dropdown contains an `li` with the text "No tutorials found",
|
||||
// so we need to check the text content to ensure the correct `li` is displayed.
|
||||
await expect(resultList.getByRole('listitem').first()).toContainText(
|
||||
/article/i
|
||||
);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await page.waitForURL(
|
||||
'https://www.freecodecamp.org/news/search/?query=article'
|
||||
);
|
||||
const title = await page.title();
|
||||
expect(title).toBe('Search - freeCodeCamp.org');
|
||||
});
|
||||
|
||||
test('should return the search results when the user clicks the search button', async ({
|
||||
page,
|
||||
isMobile
|
||||
}) => {
|
||||
test.skip(!haveApiKeys, 'This test requires Algolia API keys');
|
||||
|
||||
await search({ page, isMobile, query: 'article' });
|
||||
|
||||
// Wait for the search results to show up
|
||||
const resultList = page.getByRole('list', { name: 'Search results' });
|
||||
// Initially, the dropdown contains an `li` with the text "No tutorials found",
|
||||
// so we need to check the text content to ensure the correct `li` is displayed.
|
||||
await expect(resultList.getByRole('listitem').first()).toContainText(
|
||||
/article/i
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Submit search terms' }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
'https://www.freecodecamp.org/news/search/?query=article'
|
||||
);
|
||||
const title = await page.title();
|
||||
expect(title).toBe('Search - freeCodeCamp.org');
|
||||
});
|
||||
|
||||
test('should show an empty result list if no results found', async ({
|
||||
page,
|
||||
isMobile
|
||||
}) => {
|
||||
await mockAlgolia({ page, hitsPerPage: 0 });
|
||||
await search({ page, isMobile, query: '!@#$%^' });
|
||||
|
||||
const resultList = page.getByRole('list', { name: 'Search results' });
|
||||
await expect(resultList.getByRole('listitem')).toHaveCount(1);
|
||||
await expect(resultList.getByRole('listitem')).toHaveText(
|
||||
'No tutorials found'
|
||||
);
|
||||
});
|
||||
|
||||
test('should clear the input and hide the result dropdown when the user clicks the clear button', async ({
|
||||
page,
|
||||
isMobile
|
||||
}) => {
|
||||
const searchInput = await getSearchInput({ page, isMobile });
|
||||
await expect(searchInput).toBeVisible();
|
||||
|
||||
await searchInput.fill('test');
|
||||
await page.getByRole('button', { name: 'Clear search terms' }).click();
|
||||
|
||||
await expect(searchInput).toHaveValue('');
|
||||
await expect(
|
||||
page.getByRole('list', { name: 'Search results' })
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Search results when viewport height is greater than 768px', () => {
|
||||
test.use({
|
||||
viewport: { width: 1600, height: 1200 }
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/learn');
|
||||
|
||||
// Mock Algolia requests to prevent hitting Algolia server unnecessarily.
|
||||
// Comment out this line if you want to test against the real server.
|
||||
await mockAlgolia({ page, hitsPerPage: 8 });
|
||||
});
|
||||
|
||||
test('should display 8 items', async ({ page, isMobile }) => {
|
||||
test.skip(!haveApiKeys, 'This test requires Algolia API keys');
|
||||
|
||||
await search({ page, isMobile, query: 'article' });
|
||||
|
||||
// Wait for the search results to show up
|
||||
const results = page.getByRole('list', { name: 'Search results' });
|
||||
await expect(results.getByRole('listitem')).toHaveCount(9); // 8 results + the footer
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Search results when viewport height is equal to 768px', () => {
|
||||
test.use({
|
||||
viewport: { width: 1600, height: 768 }
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/learn');
|
||||
|
||||
// Mock Algolia requests to prevent hitting Algolia server unnecessarily.
|
||||
// Comment out this line if you want to test against the real server.
|
||||
await mockAlgolia({ page, hitsPerPage: 8 });
|
||||
});
|
||||
|
||||
test('should display 8 items', async ({ page, isMobile }) => {
|
||||
test.skip(!haveApiKeys, 'This test requires Algolia API keys');
|
||||
|
||||
await search({ page, isMobile, query: 'article' });
|
||||
|
||||
// Wait for the search results to show up
|
||||
const results = page.getByRole('list', { name: 'Search results' });
|
||||
await expect(results.getByRole('listitem')).toHaveCount(9); // 8 results + the footer
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Search results when viewport height is less than 768px', () => {
|
||||
test.use({
|
||||
viewport: { width: 1600, height: 500 }
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/learn');
|
||||
|
||||
// Mock Algolia requests to prevent hitting Algolia server unnecessarily.
|
||||
// Comment out this line if you want to test against the real server.
|
||||
await mockAlgolia({ page, hitsPerPage: 5 });
|
||||
});
|
||||
|
||||
test('should display 5 items', async ({ page, isMobile }) => {
|
||||
test.skip(!haveApiKeys, 'This test requires Algolia API keys');
|
||||
|
||||
await search({ page, isMobile, query: 'article' });
|
||||
|
||||
// Wait for the search results to show up
|
||||
const results = page.getByRole('list', { name: 'Search results' });
|
||||
await expect(results.getByRole('listitem')).toHaveCount(6); // 5 results + the footer
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user