Interview question
Write a production smoke test for Angular deep links and asset fallbacks
Uses browser and direct HTTP checks to verify nested route entry, reload, base paths, assets, genuine 404s, and API boundaries after deployment.
TL;DR
Uses browser and direct HTTP checks to verify nested route entry, reload, base paths, assets, genuine 404s, and API boundaries after deployment.
Angular deployment, Playwright, deep links, SPA rewrites, base href, assets, API 404s, content types, and release smoke testing.
Practice the problem like a real interview: restate, reason, implement, and test.
Create a deployment smoke test for an Angular app hosted at APP_URL. Verify a direct nested route renders and survives reload, its JavaScript assets return JavaScript rather than the SPA HTML fallback, a missing static asset remains 404, and an unknown API route is not rewritten to index.html. The test must run against the deployed host, not ng serve.
GET /orders/123 -> 200 HTML; Angular renders Order 123
reload /orders/123 -> still renders Order 123
GET emitted main chunk -> 200 JavaScript content type
GET /assets/missing.svg -> 404, not index.html
GET /api/does-not-exist -> API 404, not index.html
import {expect, test} from '@playwright/test';
const baseURL = process.env.APP_URL!;
test('deployed Angular routing and fallbacks are correct', async ({page, request}) => {
const direct = await page.goto(`${baseURL}/orders/123`);
expect(direct?.status()).toBe(200);
await expect(page.getByRole('heading', {name: /order 123/i})).toBeVisible();
await page.reload();
await expect(page).toHaveURL(/\/orders\/123$/);
await expect(page.getByRole('heading', {name: /order 123/i})).toBeVisible();
const scriptPath = await page.locator('script[src]').evaluateAll((scripts, appOrigin) =>
scripts
.map(script => script.getAttribute('src'))
.find(src => src && new URL(src, appOrigin).origin === new URL(appOrigin).origin),
baseURL,
);
expect(scriptPath).toBeTruthy();
const script = await request.get(new URL(scriptPath!, baseURL).toString());
expect(script.status()).toBe(200);
expect(script.headers()['content-type']).toMatch(/javascript/);
const missingAsset = await request.get(`${baseURL}/assets/missing-smoke.svg`);
expect(missingAsset.status()).toBe(404);
const missingApi = await request.get(`${baseURL}/api/does-not-exist`);
expect(missingApi.status()).toBe(404);
expect(missingApi.headers()['content-type']).not.toMatch(/text\/html/);
});
The smoke test performs a constant number of browser navigations and HTTP requests; its duration is dominated by deployed network and application startup time.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.