Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
- 'main'
pull_request:

env:
FRONT_DOOR_USERNAME: ${{ secrets.FRONT_DOOR_USERNAME }}
FRONT_DOOR_PASSWORD: ${{ secrets.FRONT_DOOR_PASSWORD }}
jobs:
playwright:
name: Run Playwright
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.key
*~
\#*
.env

# OS Specific #
###############
Expand Down Expand Up @@ -34,6 +35,7 @@ exampleSite/hugo
/coverage
*/test-results
*/playwright-report
*.bkp

# Biome
biome.rb
Expand Down
2 changes: 1 addition & 1 deletion layouts/partials/coveo-atomic.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<atomic-search-interface id="search-v2">
<atomic-search-interface id="search-v2" data-testid="search-results-page">
<atomic-search-layout>
<!-- Search/Metadata Section -->
<atomic-layout-section section="search">
Expand Down
16 changes: 15 additions & 1 deletion tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.0.0",
"private": "true",
"devDependencies": {
"@playwright/test": "1.48.0"
"@playwright/test": "1.48.0",
"dotenv": "^17.2.3"
}
}
9 changes: 7 additions & 2 deletions tests/playwright.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';

const BASE_URL = 'http://127.0.0.1';
const PORT = 1313;

dotenv.config();

export default defineConfig({
testDir: './src',
fullyParallel: true,
workers: 1,
outputDir: './test-results',
snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
reporter: [['html', { outputFolder: './playwright-report' }]],
reporter: [['html', { open: 'never', outputFolder: './playwright-report' }]],
use: {
baseURL: `${BASE_URL}:${PORT}`,
screenshots: 'only-on-failure',
Expand All @@ -29,9 +33,10 @@ export default defineConfig({
},
],
webServer: {
command: `cd ../exampleSite && hugo mod get && hugo --gc --config hugo.toml,hugo.test.toml && hugo serve --port ${PORT} --config hugo.toml,hugo.test.toml`,
command: `cd ../exampleSite && sed -i.bkp 's/disable_coveo = .*/disable_coveo = false/' hugo.toml && hugo mod get && hugo --gc --config hugo.toml,hugo.test.toml && hugo serve --port ${PORT} --config hugo.toml,hugo.test.toml`,
url: `${BASE_URL}:${PORT}`,
stdout: 'ignore',
reuseExistingServer: !process.env.CI,
},
expect: {
toHaveScreenshot: {
Expand Down
60 changes: 60 additions & 0 deletions tests/src/coveo.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect, test } from '@playwright/test';
import { mockCoveoCredentials, mockCoveoData } from './mock';
import {
buildURLFragment,
handleConsentPopup,
runSmokeTestCoveo,
waitFor,
} from './utils';

async function submitSearchQuery(page, query) {
const headerSearchBarContainer = page.getByTestId('header__search');
const searchBar = headerSearchBarContainer.locator('[part="textarea"]');
await searchBar.fill(query);
await page.keyboard.press('Enter');
await page.waitForURL(`**/search.html#q=${query}`);
await page.waitForSelector('#search-v2');
}

test.describe('Coveo test', () => {
test.beforeEach(async ({ page, request }) => {
await page.goto('/');
await page.waitForLoadState('load');
await waitFor(async () => await handleConsentPopup(page));
await mockCoveoCredentials(page, request);
});

test.afterEach(async ({ page }) => {
// Run basic smoke tests on all valid queries
if (!test.info().title.includes('invalid search query')) {
await runSmokeTestCoveo(page);
}
});

test('valid search query', async ({ page }) => {
await submitSearchQuery(page, mockCoveoData.validQuery);
});

test('invalid search query', async ({ page }) => {
await submitSearchQuery(page, mockCoveoData.invalidQuery);
const resultsPage = page.getByTestId('search-results-page');
const main = resultsPage.locator('atomic-layout-section[section="main"]');
const noResultsMessage = main.locator('[part="no-results"]');
await expect(noResultsMessage).toBeVisible();
});

test('inbound link do not reset URL', async ({ page }) => {
// Use ONLY generic filters. Do not add any product specific filters, particularly from the facet.
// If these basic filters work, then its safe to assume, adding facet filters will not reset the URL.
const endpoint = `/search.html#q=${mockCoveoData.validQuery}${buildURLFragment(mockCoveoData.filters)}`;
await page.goto(endpoint);
await page.waitForSelector('#search-v2');

// should retain the same link instead of resetting
expect(page.url()).toContain(endpoint);

// reloading should retain the same link instead of resetting
await page.reload();
expect(page.url()).toContain(endpoint);
});
});
36 changes: 36 additions & 0 deletions tests/src/mock/coveo.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect } from '@playwright/test';

export const mockCoveoData = {
validQuery: 'proxy',
invalidQuery: 'abcdefghijkl',
filters: ['numberOfResults=100', 'sortCriteria=date descending'],
};

export async function mockCoveoCredentials(page, request) {
// Get credentials
const tokenBaseURL = 'https://docs-dev.nginx.com';
const tokenEndpoint = '/api/v1/auth/search_token';
const username = process.env.FRONT_DOOR_USERNAME;
const password = process.env.FRONT_DOOR_PASSWORD;
const response = await request.get(tokenBaseURL + tokenEndpoint, {
headers: {
Authorization:
'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'),
},
});

expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);

const credentials = await response.json();

// Mock the local request to be successful, then reload the page.
await page.route(`**${tokenEndpoint}`, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(credentials),
});
});
await page.reload();
}
1 change: 1 addition & 0 deletions tests/src/mock/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './coveo.mockdata';
19 changes: 19 additions & 0 deletions tests/src/utils/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { expect } from '@playwright/test';
import { TIMEOUT } from '../constants';

export async function runSmokeTestCoveo(page) {
// Separate into variables in case we need to decouple from Coveo Atomic, but we still keep the basic structure of a search page.
const resultsPage = page.getByTestId('search-results-page');
const status = resultsPage.locator('atomic-layout-section[section="status"]');
const facet = resultsPage.locator('atomic-layout-section[section="facets"]');
const main = resultsPage.locator('atomic-layout-section[section="main"]');

await expect(status).toBeVisible();
await expect(facet).toBeVisible();
await expect(main).toBeVisible();
}

export async function runSmokeTestOnPage(page) {
/* Ensure each page follows the following dom structure */
await expect(await page.getByTestId('content').count()).toBeTruthy();
Expand All @@ -27,3 +39,10 @@ export const waitFor = async function waitFor(f, ftimeout = TIMEOUT) {
while (!f()) await sleep(ftimeout);
return f();
};

export function buildURLFragment(fragments) {
return fragments
.map((filter) => `&${filter}`)
.join('')
.replaceAll(' ', '%20');
}
Loading