Files
TS_Proto/e2e/tests/search.spec.ts
T
nilesh.pandClaude Opus 4.8 4d3eb18a6c Initial commit: TravelHub multi-tenant flight booking platform
Angular + Go + PostgreSQL sample with strict per-tenant isolation.

- Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool)
- Frontend: Angular 22 SPA (search, checkout, bookings, dashboard)
- Database: schema, seed data and demo bookings migrations
- Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e),
  plus a generated evidence report with PDF export
- Security: whitebox VAPT report under security/

Secrets (.env, tokens, DB credentials) are git-ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-13 11:38:01 +05:30

154 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from '../fixtures/test';
import { SKYLINE_AGENT } from '../fixtures/accounts';
test.beforeEach(async ({ signInAs, shell, search }) => {
await signInAs(SKYLINE_AGENT);
await shell.goToSearch();
await search.settled();
});
test.describe('Flight search', () => {
test('opens with the agency inventory already listed', async ({ search }) => {
await expect(search.results.first()).toBeVisible();
await expect(search.resultSummary).toContainText('flights');
expect(await search.totalResults()).toBeGreaterThan(0);
});
test('offers only airports this agency flies', async ({ search }) => {
const codes = await search.origin.locator('option').allInnerTexts();
expect(codes.join(' ')).toContain('JFK');
expect(codes.join(' ')).toContain('LHR');
expect(codes[0]).toContain('Any airport');
});
test('narrows results to a route', async ({ search, evidence }) => {
const before = await search.totalResults();
await evidence.step('Full inventory before filtering', { flightsVisible: before });
await search.searchRoute('JFK', 'LHR');
const after = await search.totalResults();
await evidence.step('Filtered to JFK to LHR', {
route: 'JFK to LHR',
flightsBefore: before,
flightsAfter: after,
cheapestFare: `$${(await search.card(0).fare()).toFixed(2)}`,
firstFlight: await search.card(0).flightNumber(),
});
expect(after).toBeGreaterThan(0);
expect(after).toBeLessThan(before);
// Every visible card should be on the requested route.
const cards = await search.results.all();
for (const card of cards) {
await expect(card.locator('.iata').first()).toHaveText('JFK');
await expect(card.locator('.iata').last()).toHaveText('LHR');
}
});
test('a route with no inventory shows an empty state', async ({ search }) => {
// Nothing in the seed data flies Heathrow to Heathrow.
await search.searchRoute('LHR', 'LHR');
await expect(search.emptyState).toBeVisible();
await expect(search.emptyState).toContainText('Nothing on this route yet');
await expect(search.results).toHaveCount(0);
});
test('resets back to the full list', async ({ search }) => {
await search.searchRoute('JFK', 'LHR');
const narrowed = await search.totalResults();
await search.resetButton.click();
await search.settled();
expect(await search.totalResults()).toBeGreaterThan(narrowed);
await expect(search.origin).toHaveValue('');
});
test('sorts by lowest fare', async ({ search, evidence }) => {
await search.searchRoute('JFK', 'LHR');
await search.sortBy('price');
const fares: number[] = [];
for (let i = 0; i < (await search.results.count()); i++) {
fares.push(await search.card(i).fare());
}
expect(fares.length).toBeGreaterThan(1);
expect([...fares]).toEqual([...fares].sort((a, b) => a - b));
await evidence.step('Results ordered by lowest fare', {
sortedBy: 'price',
faresOnPage: fares.map((f) => `$${f.toFixed(2)}`).join(', '),
lowest: `$${fares[0].toFixed(2)}`,
highest: `$${fares[fares.length - 1].toFixed(2)}`,
ascending: true,
});
});
test('sorts by shortest trip', async ({ search }) => {
await search.sortBy('duration');
const durations: number[] = [];
for (let i = 0; i < Math.min(5, await search.results.count()); i++) {
durations.push(await search.card(i).durationMinutes());
}
expect([...durations]).toEqual([...durations].sort((a, b) => a - b));
});
test('filters by cabin', async ({ search }) => {
await search.cabin.selectOption('business');
await search.searchButton.click();
await search.settled();
const count = await search.results.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
await expect(search.results.nth(i).locator('.fare-tags .badge').first()).toHaveText(
/business/i,
);
}
});
test('hides flights without room for the whole party', async ({ search }) => {
await search.setTravellers(9);
const count = await search.results.count();
for (let i = 0; i < count; i++) {
expect(await search.card(i).seatsLeft()).toBeGreaterThanOrEqual(9);
}
});
test('pages through results without repeating them', async ({ search }) => {
await expect(search.prevPage).toBeDisabled();
const firstPage = await search.results.first().innerText();
await search.nextPage.click();
await search.settled();
await expect(search.prevPage).toBeEnabled();
await expect(search.resultSummary).toContainText('Showing 11');
expect(await search.results.first().innerText()).not.toBe(firstPage);
await search.prevPage.click();
await search.settled();
await expect(search.resultSummary).toContainText('Showing 1–10');
});
test('a result card carries everything needed to choose', async ({ search }) => {
const card = search.card(0);
await expect(card.root.locator('.carrier-name')).not.toBeEmpty();
await expect(card.root.locator('.leg-duration')).toContainText(/\d+h|\d+m/);
await expect(card.price).toContainText('$');
await expect(card.seatsBadge).toContainText('left');
await expect(card.root.locator('.departure-date')).not.toBeEmpty();
await expect(card.selectButton).toBeEnabled();
});
});