Demo sport fishing store (rods, reels, lures, line and accessories) built with vanilla HTML, CSS and JavaScript, no frameworks or dependencies. It's meant as a practice ground for UI test automation with Playwright: it covers everything from basic interactions to the framework's advanced capabilities (network, downloads, uploads, dialogs, iframes, Shadow DOM, drag & drop, languages and regions), with deterministic text and documented intentional bugs.
Created by Luis Montoya · @afishingday. Fiskr comes from Old Norse fiskr: "fish".
Test site — not a real store. Orders, payments and the user account are simulated; nothing is sent to any server.
| Capability | Where it lives on the site |
|---|---|
| Navigation, forms, asserts | The whole site (getByRole, getByLabel, getByTestId) |
| Live search/filters/sort | Catalog (search-input, category-filter, sort-select) |
Network interception (page.route, waitForResponse) |
The catalog loads via fetch from assets/data/products.json; can be mocked, delayed or broken |
Downloads (waitForEvent("download")) |
Knots guide (static), catalog CSV, per-product spec sheet, per-order receipt |
Upload (setInputFiles) |
My account → "Request a return" (validates JPG/PNG/PDF type and ≤ 2 MB size) |
Native dialog (page.on("dialog")) |
Cart → "Clear cart" uses confirm() |
Tooltip (hover()) |
Cart → "?" icon next to Shipping (hover and focus) |
iframe (frameLocator) |
Homepage → store map (store-map, with an interactive button inside) |
| Shadow DOM | Floating help widget on every page (help-widget, custom element with an open shadow root) |
Drag & drop (dragTo) |
My account → "My favorite techniques" (reorderable list with aria-live status) |
Languages (context locale) |
ES/EN: the initial language comes from navigator.language; there's also a header selector (language-select) |
| Multi-country / currency | Country selector (country-select): CO (COP), MX (MXN), ES (EUR), US (USD) with fixed rates |
| Dependent dropdowns | Checkout: the city select is enabled and populated based on the country |
| Deterministic waits | Simulated payment: exactly 2000 ms; toasts: auto-dismiss at 4000 ms |
| Keyboard / accessibility | Arrow-key tabs, ARIA on toasts/steps/errors, aria-invalid, aria-live |
- Language (ES/EN): defaults to
navigator.language(es*→ Spanish, anything else → English), ideal for testing withbrowser.newContext({ locale: "es-CO" }). Can also be changed with thelanguage-selectin the header (reloads the page). - Country:
country-selectsets the displayed currency. Base prices are in USD and converted with fixed rates (deterministic): COP ×4000 (no decimals, "." thousands separator), MXN ×17 (MX$), EUR ×0.9 (" €" suffix, European format), USD ×1. - If the
localecarries a known region (es-MX→ MX,es-ES→ ES), the initial country adjusts itself; otherwise ES→Colombia and EN→United States. - The preference lives in
sessionStorage(keyfiskr-locale). - Product reviews are not translated (just like on a real store).
- The catalog CSV always exports prices in USD, so the download stays stable regardless of the active country.
| Data | Value |
|---|---|
| Demo account | sofia@fiskr.shop / Pesca2026! |
| Card that approves | 4111 1111 1111 1111 (any future date and CVV) |
| Card that declines | 4000 0000 0000 0002 |
| Valid coupon | BIENVENIDA10 → −10% (always recalculated) |
| Valid coupon (with a bug) | PESCADOR15 → −15% (see BUG-03) |
| Expired coupon | PROMO2024 |
| Out-of-stock product | "Glaciar 45 L marine cooler" (nevera-marina) |
| Low-stock product | "EcoScan portable sounder with GPS" (sonda-gps, 3 units) |
| Free shipping | Orders from $150 USD (otherwise $9.90 USD) |
| Tax | 8% on the discounted subtotal |
| Countries / cities | CO, MX, ES, US — 5 cities each (checkout) |
The repo ships the full suite under tests/: Playwright +
TypeScript, Page Object Model, 71 tests with per-viewport projects (mobile,
tablet, desktop, 2K and 4K), a self-contained dashboard
(tests/dashboard/index.html) and a GitHub Actions workflow that deploys to
Pages and runs the suite against the published site. The defects the suite
detects are documented in tests/BUGS.md.
Six real, reproducible defects, meant for writing tests that must
fail (or that document the bug with test.fail()):
| ID | Where | Expected behavior | Actual behavior |
|---|---|---|---|
| BUG-01 | Catalog · search | Searching cana or senuelo (no accent/ñ) should find "Cañas" and "Señuelos" |
Doesn't normalize accents: returns 0 results (reproducible in ES) |
| BUG-02 | Catalog · "price: low to high" sort | Ascending numeric order | Sorts as text: the $9.50 hooks end up last |
| BUG-03 | Cart · PESCADOR15 coupon |
The 15% discount should recalculate when the cart changes | The amount stays "frozen" at the moment it was applied |
| BUG-04 | Cart · + button |
Should not allow exceeding the available stock | Allows ordering more units than there are |
| BUG-05 | Checkout · zip code | Should require digits only | Accepts any non-empty text |
| BUG-06 | Product · "Reviews" tab | The counter should match the listed reviews | Shows one review too many (off-by-one) |
data-testidon every interactive or assertable element. Dynamic ids derive from the product id or the order id:add-to-cart-cana-spinning,qty-plus-kit-senuelos,remove-sonda-gps,order-FSK-1042,download-receipt-FSK-1042.- Correct ARIA: tabs with
role="tab"and arrow-key navigation, toasts withrole="status", tooltip withrole="tooltip", errors witharia-invalid+aria-describedby, drag & drop order announced viaaria-live. - Deterministic text: same data and language, same text. Currency formatting
is manual (doesn't depend on
Intlor the browser version). - Fixed timings: simulated payment 2000 ms; toasts 4000 ms.
- State in
sessionStorage: cart, coupon, session, orders and language/country survive navigation, but every new browser context (every test) starts from a clean store. - Deterministic downloads:
catalogo-fiskr.csv,ficha-cana-spinning.txt,comprobante-FSK-1042.txt,guia-nudos.txt. - Observable network:
GET assets/data/products.jsonon every page; great forpage.route()(mock an empty catalog, altered prices, a 500 error…).
It's a 100% static site with relative paths, ready for GitHub Pages.
Since the catalog loads via fetch, you need a local server (it won't
work by opening the file directly):
# Python
python -m http.server 8080
# Node
npx serve .Then open http://localhost:8080.
fiskr/
├── index.html # Homepage: hero, favorites, promos, map iframe
├── pages/
│ ├── products.html # Catalog with search, filter, sort and CSV
│ ├── product.html # Detail: photo, stock, tabs, spec sheet
│ ├── cart.html # Cart: coupons, tooltip, confirm() on clear
│ ├── checkout.html # Shipping (country→city) → simulated payment → confirmation
│ └── account.html # Login, orders, return (upload), drag & drop
└── assets/
├── css/styles.css # Single design system (light/dark)
├── data/products.json # Bilingual catalog served via fetch
├── img/ # Hero photo and product photos
├── downloads/ # Static downloadable files
├── frames/mapa.html # iframe content
└── js/
├── i18n.js # Languages, countries, currencies and DOM translation
├── store.js # Catalog (fetch), cart, coupons, session, orders
├── main.js # Shell: language/country, theme, nav, toasts, badge
├── widget.js # Help widget with Shadow DOM
└── *.js # One module per page