{
  "data_version": "1.0.0",
  "seed": 20261102,
  "tests": [
    {
      "id": "shop",
      "group": "Shop & pagination",
      "title": "The catalogue",
      "page": "/shop/",
      "answers": "/shop/answers.json",
      "difficulty": "Medium",
      "needs": "HTTP only",
      "what_it_tests": "Categories, subcategories, numbered pages and next links, full product pages with JSON-LD, and the traps real shops set: tracking parameters, missing trailing slashes, products listed twice, items out of stock."
    },
    {
      "id": "infinite-scroll",
      "group": "JavaScript pages",
      "title": "Infinite scroll",
      "page": "/js/infinite-scroll/",
      "answers": "/js/infinite-scroll/answers.json",
      "difficulty": "Medium",
      "needs": "Needs JS",
      "what_it_tests": "Ninety-six items arrive twelve at a time as you scroll. Stop scrolling early and you miss some."
    },
    {
      "id": "load-more",
      "group": "JavaScript pages",
      "title": "Load more button",
      "page": "/js/load-more/",
      "answers": "/js/load-more/answers.json",
      "difficulty": "Medium",
      "needs": "Needs JS",
      "what_it_tests": "Sixty items behind a button that has to be clicked five times, waiting each time for the new rows."
    },
    {
      "id": "api",
      "group": "JavaScript pages",
      "title": "Data from a JSON API",
      "page": "/js/api/",
      "answers": "/js/api/answers.json",
      "difficulty": "Easy",
      "needs": "Needs JS",
      "what_it_tests": "An empty table the page fills from a JSON request after load. Render it, or find the API."
    },
    {
      "id": "shadow-dom",
      "group": "JavaScript pages",
      "title": "Shadow DOM",
      "page": "/js/shadow-dom/",
      "answers": "/js/shadow-dom/answers.json",
      "difficulty": "Hard",
      "needs": "Needs JS",
      "what_it_tests": "Reviews inside an open shadow root. document.querySelector can't see them; your selectors have to pierce it."
    },
    {
      "id": "iframe",
      "group": "JavaScript pages",
      "title": "Same-origin iframe",
      "page": "/js/iframe/",
      "answers": "/js/iframe/answers.json",
      "difficulty": "Medium",
      "needs": "Needs JS",
      "what_it_tests": "A store list that lives in an iframe, rendered by script inside the frame."
    },
    {
      "id": "spa",
      "group": "JavaScript pages",
      "title": "Client-rendered app",
      "page": "/js/spa/",
      "answers": "/js/spa/answers.json",
      "difficulty": "Hard",
      "needs": "Needs JS",
      "what_it_tests": "A single-page app that changes the URL with pushState. Every item has its own address, and none of them exists as HTML."
    },
    {
      "id": "lazy-images",
      "group": "JavaScript pages",
      "title": "Lazy-loaded images",
      "page": "/js/lazy-images/",
      "answers": "/js/lazy-images/answers.json",
      "difficulty": "Medium",
      "needs": "Needs JS",
      "what_it_tests": "Forty pictures that show a placeholder until they scroll into view. The real address only appears then."
    },
    {
      "id": "rate-limit",
      "group": "Bad days",
      "title": "Rate limit",
      "page": "/bad/rate-limit/",
      "answers": "/bad/rate-limit/answers.json",
      "difficulty": "Medium",
      "needs": "HTTP only",
      "what_it_tests": "Twenty-one pages that answer 429 with Retry-After once you pass twelve requests in a ten-second window."
    },
    {
      "id": "slow",
      "group": "Bad days",
      "title": "Slow pages",
      "page": "/bad/slow/",
      "answers": "/bad/slow/answers.json",
      "difficulty": "Easy",
      "needs": "HTTP only",
      "what_it_tests": "Pages that take one, three and ten seconds to answer. A short timeout loses the slow one."
    },
    {
      "id": "flaky",
      "group": "Bad days",
      "title": "Pages that fail once",
      "page": "/bad/flaky/",
      "answers": "/bad/flaky/answers.json",
      "difficulty": "Medium",
      "needs": "HTTP only",
      "what_it_tests": "Every fourth item answers 503 the first time you ask, then works. One page always fails."
    },
    {
      "id": "redirects",
      "group": "Bad days",
      "title": "Redirects",
      "page": "/bad/redirects/",
      "answers": "/bad/redirects/answers.json",
      "difficulty": "Easy",
      "needs": "HTTP only",
      "what_it_tests": "A 301, 302, 307, 308 chain, a loop that never ends, and a meta refresh."
    },
    {
      "id": "soft-404",
      "group": "Bad days",
      "title": "Soft 404s",
      "page": "/bad/soft-404/",
      "answers": "/bad/soft-404/answers.json",
      "difficulty": "Medium",
      "needs": "HTTP only",
      "what_it_tests": "Ten product links. Three answer 200 but say the product is gone; one is a real 404."
    },
    {
      "id": "redesign",
      "group": "Bad days",
      "title": "Redesign switch",
      "page": "/redesign/",
      "answers": "/redesign/answers.json",
      "difficulty": "Hard",
      "needs": "HTTP only",
      "what_it_tests": "The same fifty products in two layouts with every class renamed, plus a crossed-out price and a shipping charge to lure the wrong number."
    },
    {
      "id": "login",
      "group": "Forms & logins",
      "title": "Login and members area",
      "page": "/forms/login/",
      "answers": "/forms/login/answers.json",
      "difficulty": "Medium",
      "needs": "Needs cookies",
      "what_it_tests": "Log in with the credentials on the page, keep the session cookie, and read three members-only pages that bounce you to the login without it."
    },
    {
      "id": "csrf",
      "group": "Forms & logins",
      "title": "CSRF token",
      "page": "/forms/csrf/",
      "answers": "/forms/csrf/answers.json",
      "difficulty": "Medium",
      "needs": "Needs cookies",
      "what_it_tests": "A form whose hidden token must come back with the cookie it was issued with. Replay it wrong and you get a 403."
    },
    {
      "id": "search",
      "group": "Forms & logins",
      "title": "Search form",
      "page": "/forms/search/",
      "answers": "/forms/search/answers.json",
      "difficulty": "Easy",
      "needs": "HTTP only",
      "what_it_tests": "A GET search with paged results. Build the query string and follow the pages."
    },
    {
      "id": "multi-step",
      "group": "Forms & logins",
      "title": "Multi-step form",
      "page": "/forms/multi-step/",
      "answers": "/forms/multi-step/answers.json",
      "difficulty": "Hard",
      "needs": "Needs cookies",
      "what_it_tests": "Three steps, each a POST, carrying state in a signed cookie, ending in a reference code you can check."
    },
    {
      "id": "robots",
      "group": "Manners",
      "title": "robots.txt",
      "page": "/robots-test/",
      "answers": "/robots-answers.json",
      "difficulty": "Easy",
      "needs": "HTTP only",
      "what_it_tests": "A linked area that robots.txt disallows. A polite scraper fetches none of it."
    }
  ]
}
