{
  "schemaVersion": 1,
  "provenance": {
    "commit": "254e5d12ababb5d8f573ceb7443753ec275f90c4",
    "sourceSha256": "5e99064ba7f1d42a8a82442c01e74f01e173f64ccc38170ae2b052b1096e5ca0"
  },
  "frameworks": [
    "frameworks/react.js",
    "frameworks/vue.js",
    "frameworks/svelte.js"
  ],
  "cases": [
    {
      "id": "ref-duplicate",
      "title": "Same-name controls retain exact identity",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Same-name controls retain exact identity",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.filter(e=>e.role==='button').length === 2",
          "capture": {
            "a": "result.elements.filter(e=>e.role==='button')[0].ref",
            "b": "result.elements.filter(e=>e.role==='button')[1].ref"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{b}}",
            "timeout": 200
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-duplicate.html"
    },
    {
      "id": "ref-moved",
      "title": "Moving the same node preserves its reference",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Moving the same node preserves its reference",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.filter(e=>e.role==='button').length === 2",
          "capture": {
            "a": "result.elements.filter(e=>e.role==='button')[0].ref",
            "b": "result.elements.filter(e=>e.role==='button')[1].ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "document.body.append(a)"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{a}}",
            "timeout": 200
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-moved.html"
    },
    {
      "id": "ref-renamed",
      "title": "Renaming the same node preserves its reference",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Renaming the same node preserves its reference",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.filter(e=>e.role==='button').length === 2",
          "capture": {
            "a": "result.elements.filter(e=>e.role==='button')[0].ref",
            "b": "result.elements.filter(e=>e.role==='button')[1].ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "a.textContent='Renamed'"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{a}}",
            "timeout": 200
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-renamed.html"
    },
    {
      "id": "ref-clone",
      "title": "An identical clone cannot inherit a detached reference",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "An identical clone cannot inherit a detached reference",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.filter(e=>e.role==='button').length === 2",
          "capture": {
            "a": "result.elements.filter(e=>e.role==='button')[0].ref",
            "b": "result.elements.filter(e=>e.role==='button')[1].ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "a.replaceWith(a.cloneNode(true))"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{a}}",
            "timeout": 200
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#b"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.b)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-clone.html"
    },
    {
      "id": "ref-removed",
      "title": "Removing one duplicate cannot redirect its reference",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Removing one duplicate cannot redirect its reference",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.filter(e=>e.role==='button').length === 2",
          "capture": {
            "a": "result.elements.filter(e=>e.role==='button')[0].ref",
            "b": "result.elements.filter(e=>e.role==='button')[1].ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "a.remove()"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{a}}",
            "timeout": 200
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#b"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.b)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-removed.html"
    },
    {
      "id": "ref-replaced",
      "title": "Replacing and reversing duplicates invalidates old references",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Replacing and reversing duplicates invalidates old references",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.filter(e=>e.role==='button').length === 2",
          "capture": {
            "a": "result.elements.filter(e=>e.role==='button')[0].ref",
            "b": "result.elements.filter(e=>e.role==='button')[1].ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "const x=a.cloneNode(true), y=b.cloneNode(true); a.remove(); b.remove(); document.body.append(y,x)"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{b}}",
            "timeout": 200
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#b"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.b)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-replaced.html"
    },
    {
      "id": "ref-find-refresh",
      "title": "Find installs the reference mapping used by the next action",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Alpha</button><button id=\"b\" onclick=\"__lab.state.b++\">Beta</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Find installs the reference mapping used by the next action",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          }
        },
        {
          "method": "find",
          "params": {
            "text": "Beta",
            "format": "json"
          },
          "verify": "result.elements.length===1 && result.elements[0].name==='Beta'",
          "capture": {
            "ref": "result.elements[0].ref"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{ref}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-find-refresh.html"
    },
    {
      "id": "ref-unknown",
      "title": "Unknown reference fails and a valid selector still works",
      "category": "References",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Unknown reference fails and a valid selector still works",
      "steps": [
        {
          "method": "click",
          "params": {
            "ref": "e99999"
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#a"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.a)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/ref-unknown.html"
    },
    {
      "id": "selector-attribute",
      "title": "Target by attribute selector",
      "category": "Selectors",
      "html": "<section><button onclick=\"__lab.state.a++\">Decoy</button><button id=\"odd:id\" data-key=\"special\" onclick=\"__lab.state.b++\">Target</button></section>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Target by attribute selector",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "[data-key=\"special\"]"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/selector-attribute.html"
    },
    {
      "id": "selector-escaped",
      "title": "Target by escaped selector",
      "category": "Selectors",
      "html": "<section><button onclick=\"__lab.state.a++\">Decoy</button><button id=\"odd:id\" data-key=\"special\" onclick=\"__lab.state.b++\">Target</button></section>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Target by escaped selector",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#odd\\:id"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/selector-escaped.html"
    },
    {
      "id": "selector-combinator",
      "title": "Target by combinator selector",
      "category": "Selectors",
      "html": "<section><button onclick=\"__lab.state.a++\">Decoy</button><button id=\"odd:id\" data-key=\"special\" onclick=\"__lab.state.b++\">Target</button></section>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Target by combinator selector",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "section > button:nth-child(2)"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/selector-combinator.html"
    },
    {
      "id": "selector-compound",
      "title": "Target by compound selector",
      "category": "Selectors",
      "html": "<section><button onclick=\"__lab.state.a++\">Decoy</button><button id=\"odd:id\" data-key=\"special\" onclick=\"__lab.state.b++\">Target</button></section>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Target by compound selector",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "button[data-key=\"special\"]"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/selector-compound.html"
    },
    {
      "id": "selector-invalid",
      "title": "Invalid CSS selector reports an error without damaging the page",
      "category": "Selectors",
      "html": "<button id=\"a\" onclick=\"__lab.state.a++\">Save</button><button id=\"b\" onclick=\"__lab.state.b++\">Save</button>",
      "css": "",
      "setup": "__lab.state = {a:0,b:0};",
      "description": "Invalid CSS selector reports an error without damaging the page",
      "steps": [
        {
          "method": "count",
          "params": {
            "selector": "###"
          },
          "error": "selector"
        },
        {
          "method": "count",
          "params": {
            "selector": "button"
          },
          "expect": 2
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.a,__lab.state.b])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/selector-invalid.html"
    },
    {
      "id": "snapshot-role-h1",
      "title": "h1 has the documented snapshot role",
      "category": "Snapshots",
      "html": "<h1>Target</h1>",
      "css": "",
      "setup": "",
      "description": "h1 has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='heading' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-h1.html"
    },
    {
      "id": "snapshot-role-h2",
      "title": "h2 has the documented snapshot role",
      "category": "Snapshots",
      "html": "<h2>Target</h2>",
      "css": "",
      "setup": "",
      "description": "h2 has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='heading' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-h2.html"
    },
    {
      "id": "snapshot-role-h3",
      "title": "h3 has the documented snapshot role",
      "category": "Snapshots",
      "html": "<h3>Target</h3>",
      "css": "",
      "setup": "",
      "description": "h3 has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='heading' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-h3.html"
    },
    {
      "id": "snapshot-role-h4",
      "title": "h4 has the documented snapshot role",
      "category": "Snapshots",
      "html": "<h4>Target</h4>",
      "css": "",
      "setup": "",
      "description": "h4 has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='heading' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-h4.html"
    },
    {
      "id": "snapshot-role-h5",
      "title": "h5 has the documented snapshot role",
      "category": "Snapshots",
      "html": "<h5>Target</h5>",
      "css": "",
      "setup": "",
      "description": "h5 has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='heading' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-h5.html"
    },
    {
      "id": "snapshot-role-h6",
      "title": "h6 has the documented snapshot role",
      "category": "Snapshots",
      "html": "<h6>Target</h6>",
      "css": "",
      "setup": "",
      "description": "h6 has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='heading' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-h6.html"
    },
    {
      "id": "snapshot-role-button",
      "title": "button has the documented snapshot role",
      "category": "Snapshots",
      "html": "<button>Target</button>",
      "css": "",
      "setup": "",
      "description": "button has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='button' && e.name==='Target')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-button.html"
    },
    {
      "id": "snapshot-role-textarea",
      "title": "textarea has the documented snapshot role",
      "category": "Snapshots",
      "html": "<textarea aria-label=\"Target\">Initial</textarea>",
      "css": "",
      "setup": "",
      "description": "textarea has the documented snapshot role",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Target' && e.value==='Initial')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-role-textarea.html"
    },
    {
      "id": "snapshot-label-for",
      "title": "Accessible name: label-for",
      "category": "Snapshots",
      "html": "<label for=\"field\">Account name</label><input id=\"field\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: label-for",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-label-for.html"
    },
    {
      "id": "snapshot-label-wrap",
      "title": "Accessible name: label-wrap",
      "category": "Snapshots",
      "html": "<label>Account name<input id=\"field\"></label>",
      "css": "",
      "setup": "",
      "description": "Accessible name: label-wrap",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-label-wrap.html"
    },
    {
      "id": "snapshot-placeholder",
      "title": "Accessible name: placeholder",
      "category": "Snapshots",
      "html": "<input id=\"field\" placeholder=\"Account name\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: placeholder",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-placeholder.html"
    },
    {
      "id": "snapshot-title",
      "title": "Accessible name: title",
      "category": "Snapshots",
      "html": "<input id=\"field\" title=\"Account name\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: title",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-title.html"
    },
    {
      "id": "snapshot-aria-label",
      "title": "Accessible name: aria-label",
      "category": "Snapshots",
      "html": "<input id=\"field\" aria-label=\"Account name\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: aria-label",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-aria-label.html"
    },
    {
      "id": "snapshot-labelledby",
      "title": "Accessible name: labelledby",
      "category": "Snapshots",
      "html": "<span id=\"name\">Account name</span><input id=\"field\" aria-labelledby=\"name\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: labelledby",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-labelledby.html"
    },
    {
      "id": "snapshot-labelledby-list",
      "title": "Accessible name: labelledby-list",
      "category": "Snapshots",
      "html": "<span id=\"first\">Account</span><span id=\"last\">name</span><input id=\"field\" aria-labelledby=\"first last\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: labelledby-list",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-labelledby-list.html"
    },
    {
      "id": "snapshot-empty-label-fallback",
      "title": "Accessible name: empty-label-fallback",
      "category": "Snapshots",
      "html": "<label for=\"field\"></label><input id=\"field\" title=\"Account name\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: empty-label-fallback",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-empty-label-fallback.html"
    },
    {
      "id": "snapshot-name-precedence",
      "title": "Accessible name: name-precedence",
      "category": "Snapshots",
      "html": "<span id=\"name\">Account name</span><input id=\"field\" aria-label=\"Other\" aria-labelledby=\"name\">",
      "css": "",
      "setup": "",
      "description": "Accessible name: name-precedence",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='textbox' && e.name==='Account name')"
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-name-precedence.html"
    },
    {
      "id": "snapshot-nested-text",
      "title": "Preserve nested-text",
      "category": "Snapshots",
      "html": "<p>Alpha <span>beta <strong>gamma</strong></span> delta</p>",
      "css": "",
      "setup": "",
      "description": "Preserve nested-text",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.text==='Alpha beta gamma delta')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-nested-text.html"
    },
    {
      "id": "snapshot-bare-text",
      "title": "Preserve bare-text",
      "category": "Snapshots",
      "html": "<div><div>Saved successfully</div></div>",
      "css": "",
      "setup": "",
      "description": "Preserve bare-text",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.text==='Saved successfully')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-bare-text.html"
    },
    {
      "id": "snapshot-unicode",
      "title": "Preserve unicode",
      "category": "Snapshots",
      "html": "<p>héllo 日本語 🎉 مرحبا é</p>",
      "css": "",
      "setup": "",
      "description": "Preserve unicode",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.text==='héllo 日本語 🎉 مرحبا é')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-unicode.html"
    },
    {
      "id": "snapshot-whitespace",
      "title": "Preserve whitespace",
      "category": "Snapshots",
      "html": "<p> Alpha\n  beta\t gamma </p>",
      "css": "",
      "setup": "",
      "description": "Preserve whitespace",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.text==='Alpha beta gamma')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-whitespace.html"
    },
    {
      "id": "snapshot-wrapper-dedup",
      "title": "A link-only list item is represented once",
      "category": "Snapshots",
      "html": "<ul><li><a href=\"#target\">Only link</a></li></ul>",
      "css": "",
      "setup": "",
      "description": "A link-only list item is represented once",
      "steps": [
        {
          "method": "find",
          "params": {
            "text": "Only link",
            "format": "json"
          },
          "verify": "result.elements.length===1 && result.elements[0].role==='link'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-wrapper-dedup.html"
    },
    {
      "id": "snapshot-scope",
      "title": "Scoped snapshots exclude neighboring content",
      "category": "Snapshots",
      "html": "<section id=\"scope\"><div><div><button>Inside</button></div></div></section><button>Outside</button>",
      "css": "",
      "setup": "",
      "description": "Scoped snapshots exclude neighboring content",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "selector": "#scope",
            "format": "json"
          },
          "verify": "result.elements.length===1 && result.elements[0].name==='Inside'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-scope.html"
    },
    {
      "id": "snapshot-image-link",
      "title": "Image alt supplies the image-only link name",
      "category": "Snapshots",
      "html": "<a href=\"#target\"><img src=\"../assets/pattern.svg\" alt=\"Open chart\"></a>",
      "css": "",
      "setup": "",
      "description": "Image alt supplies the image-only link name",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.role==='link' && e.name==='Open chart')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-image-link.html"
    },
    {
      "id": "snapshot-aria-hidden",
      "title": "ARIA-hidden controls are omitted but remain DOM-addressable",
      "category": "Snapshots",
      "html": "<div aria-hidden=\"true\"><button id=\"hidden\" onclick=\"__lab.state.clicked=true\">Secret</button></div><button>Visible</button>",
      "css": "",
      "setup": "",
      "description": "ARIA-hidden controls are omitted but remain DOM-addressable",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "!result.elements.some(e=>e.name==='Secret')"
        },
        {
          "method": "click",
          "params": {
            "selector": "#hidden"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-aria-hidden.html"
    },
    {
      "id": "snapshot-count-0",
      "title": "Snapshot default limit with 0 controls",
      "category": "Boundaries",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<0;i++){const b=document.createElement('button');b.textContent='Item '+i;document.body.append(b)}",
      "description": "Snapshot default limit with 0 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.length===0 && Boolean(result.truncated)===false"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-count-0.html"
    },
    {
      "id": "snapshot-count-1",
      "title": "Snapshot default limit with 1 controls",
      "category": "Boundaries",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<1;i++){const b=document.createElement('button');b.textContent='Item '+i;document.body.append(b)}",
      "description": "Snapshot default limit with 1 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.length===1 && Boolean(result.truncated)===false"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-count-1.html"
    },
    {
      "id": "snapshot-count-999",
      "title": "Snapshot default limit with 999 controls",
      "category": "Boundaries",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<999;i++){const b=document.createElement('button');b.textContent='Item '+i;document.body.append(b)}",
      "description": "Snapshot default limit with 999 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.length===999 && Boolean(result.truncated)===false"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-count-999.html"
    },
    {
      "id": "snapshot-count-1000",
      "title": "Snapshot default limit with 1000 controls",
      "category": "Boundaries",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<1000;i++){const b=document.createElement('button');b.textContent='Item '+i;document.body.append(b)}",
      "description": "Snapshot default limit with 1000 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.length===1000 && Boolean(result.truncated)===false"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-count-1000.html"
    },
    {
      "id": "snapshot-count-1001",
      "title": "Snapshot default limit with 1001 controls",
      "category": "Boundaries",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<1001;i++){const b=document.createElement('button');b.textContent='Item '+i;document.body.append(b)}",
      "description": "Snapshot default limit with 1001 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.length===1000 && Boolean(result.truncated)===true"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-count-1001.html"
    },
    {
      "id": "snapshot-max-0",
      "title": "Explicit snapshot max=0",
      "category": "Boundaries",
      "html": "<button>A</button><button>B</button>",
      "css": "",
      "setup": "",
      "description": "Explicit snapshot max=0",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "max": 0,
            "format": "json"
          },
          "verify": "result.elements.length===0 && Boolean(result.truncated)===true"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-max-0.html"
    },
    {
      "id": "snapshot-max-1",
      "title": "Explicit snapshot max=1",
      "category": "Boundaries",
      "html": "<button>A</button><button>B</button>",
      "css": "",
      "setup": "",
      "description": "Explicit snapshot max=1",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "max": 1,
            "format": "json"
          },
          "verify": "result.elements.length===1 && Boolean(result.truncated)===true"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-max-1.html"
    },
    {
      "id": "snapshot-max-3",
      "title": "Explicit snapshot max=3",
      "category": "Boundaries",
      "html": "<button>A</button><button>B</button>",
      "css": "",
      "setup": "",
      "description": "Explicit snapshot max=3",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "max": 3,
            "format": "json"
          },
          "verify": "result.elements.length===2 && Boolean(result.truncated)===false"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-max-3.html"
    },
    {
      "id": "snapshot-name-79",
      "title": "name truncation at 79 characters",
      "category": "Boundaries",
      "html": "<button>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</button>",
      "css": "",
      "setup": "",
      "description": "name truncation at 79 characters",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements[0].name==='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-name-79.html"
    },
    {
      "id": "snapshot-name-80",
      "title": "name truncation at 80 characters",
      "category": "Boundaries",
      "html": "<button>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</button>",
      "css": "",
      "setup": "",
      "description": "name truncation at 80 characters",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements[0].name==='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-name-80.html"
    },
    {
      "id": "snapshot-name-81",
      "title": "name truncation at 81 characters",
      "category": "Boundaries",
      "html": "<button>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</button>",
      "css": "",
      "setup": "",
      "description": "name truncation at 81 characters",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements[0].name==='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx…'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-name-81.html"
    },
    {
      "id": "snapshot-text-199",
      "title": "text truncation at 199 characters",
      "category": "Boundaries",
      "html": "<p>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>",
      "css": "",
      "setup": "",
      "description": "text truncation at 199 characters",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements[0].text==='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-text-199.html"
    },
    {
      "id": "snapshot-text-200",
      "title": "text truncation at 200 characters",
      "category": "Boundaries",
      "html": "<p>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>",
      "css": "",
      "setup": "",
      "description": "text truncation at 200 characters",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements[0].text==='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-text-200.html"
    },
    {
      "id": "snapshot-text-201",
      "title": "text truncation at 201 characters",
      "category": "Boundaries",
      "html": "<p>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>",
      "css": "",
      "setup": "",
      "description": "text truncation at 201 characters",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements[0].text==='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx…'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-text-201.html"
    },
    {
      "id": "snapshot-text-format",
      "title": "Text snapshots escape names and preserve link destinations",
      "category": "Snapshots",
      "html": "<a href=\"?value=1#part\">A &quot;quoted&quot; link</a>",
      "css": "",
      "setup": "",
      "description": "Text snapshots escape names and preserve link destinations",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "text"
          },
          "verify": "typeof result==='string' && result.includes('A \\\\\"quoted\\\\\" link') && result.includes('?value=1#part')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/snapshot-text-format.html"
    },
    {
      "id": "find-anchored",
      "title": "Find regex anchors match an individual name field",
      "category": "Selectors",
      "html": "<a href=\"#long-destination\">Exact</a><button>Not Exact</button>",
      "css": "",
      "setup": "",
      "description": "Find regex anchors match an individual name field",
      "steps": [
        {
          "method": "find",
          "params": {
            "regex": "^Exact$",
            "format": "json"
          },
          "verify": "result.elements.length===1 && result.elements[0].name==='Exact'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/find-anchored.html"
    },
    {
      "id": "find-invalid-regex",
      "title": "Invalid regular expression is bounded and recoverable",
      "category": "Selectors",
      "html": "<button>Valid</button>",
      "css": "",
      "setup": "",
      "description": "Invalid regular expression is bounded and recoverable",
      "steps": [
        {
          "method": "find",
          "params": {
            "regex": "("
          },
          "error": ""
        },
        {
          "method": "count",
          "params": {
            "selector": "button"
          },
          "expect": 1
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/find-invalid-regex.html"
    },
    {
      "id": "attributes-properties",
      "title": "Attribute reads distinguish DOM attributes from live values",
      "category": "Extraction",
      "html": "<input id=\"field\" value=\"original\" data-empty=\"\">",
      "css": "",
      "setup": "",
      "description": "Attribute reads distinguish DOM attributes from live values",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "changed"
          }
        },
        {
          "method": "attr",
          "params": {
            "selector": "#field",
            "name": "value"
          },
          "expect": "original"
        },
        {
          "method": "attr",
          "params": {
            "selector": "#field",
            "name": "data-empty"
          },
          "expect": ""
        },
        {
          "method": "attr",
          "params": {
            "selector": "#field",
            "name": "absent"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "changed"
          }
        },
        {
          "method": "html",
          "params": {
            "selector": "#field"
          },
          "verify": "result.startsWith('<input') && result.includes('value=\"original\"')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/attributes-properties.html"
    },
    {
      "id": "input-text-1",
      "title": "Fill text: ''",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"text\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill text: ''",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": ""
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": ""
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='text'",
          "reason": "Native input type text is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-text-1.html"
    },
    {
      "id": "input-text-2",
      "title": "Fill text: 'replacement'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"text\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill text: 'replacement'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "replacement"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "replacement"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='text'",
          "reason": "Native input type text is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-text-2.html"
    },
    {
      "id": "input-text-3",
      "title": "Fill text: ' héllo 日本語 🎉 مرحبا '",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"text\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill text: ' héllo 日本語 🎉 مرحبا '",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": " héllo 日本語 🎉 مرحبا "
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": " héllo 日本語 🎉 مرحبا "
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='text'",
          "reason": "Native input type text is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-text-3.html"
    },
    {
      "id": "input-search-1",
      "title": "Fill search: 'query'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"search\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill search: 'query'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "query"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "query"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='search'",
          "reason": "Native input type search is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-search-1.html"
    },
    {
      "id": "input-tel-1",
      "title": "Fill tel: '+1 555 0100'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"tel\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill tel: '+1 555 0100'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "+1 555 0100"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "+1 555 0100"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='tel'",
          "reason": "Native input type tel is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-tel-1.html"
    },
    {
      "id": "input-password-1",
      "title": "Fill password: 'p@ss-秘密'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"password\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill password: 'p@ss-秘密'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "p@ss-秘密"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "p@ss-秘密"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='password'",
          "reason": "Native input type password is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-password-1.html"
    },
    {
      "id": "input-email-1",
      "title": "Fill email: 'qa@example.test'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"email\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill email: 'qa@example.test'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "qa@example.test"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "qa@example.test"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='email'",
          "reason": "Native input type email is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-email-1.html"
    },
    {
      "id": "input-email-2",
      "title": "Fill email: 'invalid'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"email\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill email: 'invalid'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "invalid"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "invalid"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='email'",
          "reason": "Native input type email is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-email-2.html"
    },
    {
      "id": "input-url-1",
      "title": "Fill url: 'https://example.test/a?q=1'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"url\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill url: 'https://example.test/a?q=1'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "https://example.test/a?q=1"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "https://example.test/a?q=1"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='url'",
          "reason": "Native input type url is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-url-1.html"
    },
    {
      "id": "input-url-2",
      "title": "Fill url: 'invalid'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"url\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill url: 'invalid'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "invalid"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "invalid"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='url'",
          "reason": "Native input type url is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-url-2.html"
    },
    {
      "id": "input-number-1",
      "title": "Fill number: '0'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"number\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill number: '0'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "0"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "0"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='number'",
          "reason": "Native input type number is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-number-1.html"
    },
    {
      "id": "input-number-2",
      "title": "Fill number: '-12'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"number\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill number: '-12'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "-12"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "-12"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='number'",
          "reason": "Native input type number is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-number-2.html"
    },
    {
      "id": "input-number-3",
      "title": "Fill number: '1.5'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"number\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill number: '1.5'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "1.5"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "1.5"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='number'",
          "reason": "Native input type number is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-number-3.html"
    },
    {
      "id": "input-number-4",
      "title": "Fill number: '1e3'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"number\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill number: '1e3'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "1e3"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "1e3"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='number'",
          "reason": "Native input type number is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-number-4.html"
    },
    {
      "id": "input-range-1",
      "title": "Fill range: '0'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"range\" min=\"0\" max=\"100\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill range: '0'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "0"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "0"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='range'",
          "reason": "Native input type range is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-range-1.html"
    },
    {
      "id": "input-range-2",
      "title": "Fill range: '50'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"range\" min=\"0\" max=\"100\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill range: '50'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "50"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "50"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='range'",
          "reason": "Native input type range is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-range-2.html"
    },
    {
      "id": "input-range-3",
      "title": "Fill range: '100'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"range\" min=\"0\" max=\"100\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill range: '100'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "100"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "100"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='range'",
          "reason": "Native input type range is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-range-3.html"
    },
    {
      "id": "input-date-1",
      "title": "Fill date: '2024-02-29'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"date\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill date: '2024-02-29'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "2024-02-29"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "2024-02-29"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='date'",
          "reason": "Native input type date is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-date-1.html"
    },
    {
      "id": "input-month-1",
      "title": "Fill month: '2026-10'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"month\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill month: '2026-10'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "2026-10"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "2026-10"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='month'",
          "reason": "Native input type month is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-month-1.html"
    },
    {
      "id": "input-week-1",
      "title": "Fill week: '2026-W44'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"week\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill week: '2026-W44'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "2026-W44"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "2026-W44"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='week'",
          "reason": "Native input type week is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-week-1.html"
    },
    {
      "id": "input-time-1",
      "title": "Fill time: '13:45'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"time\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill time: '13:45'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "13:45"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "13:45"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='time'",
          "reason": "Native input type time is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-time-1.html"
    },
    {
      "id": "input-datetime-local-1",
      "title": "Fill datetime-local: '2026-10-28T13:45'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"datetime-local\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill datetime-local: '2026-10-28T13:45'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "2026-10-28T13:45"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "2026-10-28T13:45"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='datetime-local'",
          "reason": "Native input type datetime-local is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-datetime-local-1.html"
    },
    {
      "id": "input-color-1",
      "title": "Fill color: '#336699'",
      "category": "Inputs",
      "html": "<label for=\"field\">Value</label><input id=\"field\" type=\"color\"><input id=\"neighbor\" value=\"untouched\">",
      "css": "",
      "setup": "",
      "description": "Fill color: '#336699'",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "#336699"
          },
          "expect": null
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "#336699"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (neighbor.value)})"
          },
          "expect": {
            "value": "untouched"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.type==='color'",
          "reason": "Native input type color is not supported by this engine"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/input-color-1.html"
    },
    {
      "id": "fill-textarea",
      "title": "Replace textarea contents",
      "category": "Inputs",
      "html": "<textarea id=\"field\">old</textarea>",
      "css": "",
      "setup": "",
      "description": "Replace textarea contents",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "line one\nline two\tend"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.isContentEditable ? field.textContent : field.value)})"
          },
          "expect": {
            "value": "line one\nline two\tend"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-textarea.html"
    },
    {
      "id": "fill-contenteditable",
      "title": "Replace contenteditable contents",
      "category": "Inputs",
      "html": "<div id=\"field\" contenteditable>old <b>markup</b></div>",
      "css": "",
      "setup": "",
      "description": "Replace contenteditable contents",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "new content"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.isContentEditable ? field.textContent : field.value)})"
          },
          "expect": {
            "value": "new content"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-contenteditable.html"
    },
    {
      "id": "fill-empty-contenteditable",
      "title": "Replace empty-contenteditable contents",
      "category": "Inputs",
      "html": "<div id=\"field\" contenteditable></div>",
      "css": "",
      "setup": "",
      "description": "Replace empty-contenteditable contents",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "héllo 🎉"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.isContentEditable ? field.textContent : field.value)})"
          },
          "expect": {
            "value": "héllo 🎉"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-empty-contenteditable.html"
    },
    {
      "id": "fill-plaintext",
      "title": "Replace plaintext contents",
      "category": "Inputs",
      "html": "<div id=\"field\" contenteditable=\"plaintext-only\">old</div>",
      "css": "",
      "setup": "",
      "description": "Replace plaintext contents",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "new content"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.isContentEditable ? field.textContent : field.value)})"
          },
          "expect": {
            "value": "new content"
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "field.isContentEditable",
          "reason": "plaintext-only contenteditable unsupported"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-plaintext.html"
    },
    {
      "id": "type-ascii",
      "title": "Type ascii text with per-character events",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start:\">",
      "css": "",
      "setup": "",
      "description": "Type ascii text with per-character events",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "abc"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start:abc"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 3
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/type-ascii.html"
    },
    {
      "id": "type-emoji",
      "title": "Type emoji text with per-character events",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start:\">",
      "css": "",
      "setup": "",
      "description": "Type emoji text with per-character events",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "a🎉b"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start:a🎉b"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 3
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/type-emoji.html"
    },
    {
      "id": "type-combining",
      "title": "Type combining text with per-character events",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start:\">",
      "css": "",
      "setup": "",
      "description": "Type combining text with per-character events",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "é"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start:é"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 2
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/type-combining.html"
    },
    {
      "id": "type-empty",
      "title": "Type empty text with per-character events",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start:\">",
      "css": "",
      "setup": "",
      "description": "Type empty text with per-character events",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": ""
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start:"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/type-empty.html"
    },
    {
      "id": "type-plus",
      "title": "Type plus text with per-character events",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start:\">",
      "css": "",
      "setup": "",
      "description": "Type plus text with per-character events",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "+"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start:+"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='field'&&e.type==='input').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/type-plus.html"
    },
    {
      "id": "fill-disabled-control",
      "title": "Reject a disabled control",
      "category": "Inputs",
      "html": "<input id=\"field\" disabled value=\"unchanged\">",
      "css": "",
      "setup": "",
      "description": "Reject a disabled control",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "bad",
            "timeout": 150
          },
          "error": "disabled"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "unchanged"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='input').length)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-disabled-control.html"
    },
    {
      "id": "fill-disabled-fieldset",
      "title": "Reject a disabled fieldset",
      "category": "Inputs",
      "html": "<fieldset disabled><input id=\"field\" value=\"unchanged\"></fieldset>",
      "css": "",
      "setup": "",
      "description": "Reject a disabled fieldset",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "bad",
            "timeout": 150
          },
          "error": "disabled"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "unchanged"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='input').length)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-disabled-fieldset.html"
    },
    {
      "id": "fieldset-legend",
      "title": "First legend control remains enabled in a disabled fieldset",
      "category": "Inputs",
      "html": "<fieldset disabled><legend><input id=\"field\"></legend><input id=\"other\"></fieldset>",
      "css": "",
      "setup": "",
      "description": "First legend control remains enabled in a disabled fieldset",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "allowed"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "allowed"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (other.value)})"
          },
          "expect": {
            "value": ""
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fieldset-legend.html"
    },
    {
      "id": "readonly",
      "title": "Readonly input should resist editing",
      "category": "Inputs",
      "html": "<input id=\"field\" readonly value=\"original\">",
      "css": "",
      "setup": "",
      "description": "Readonly input should resist editing",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "changed",
            "timeout": 150
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "original"
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/readonly.html"
    },
    {
      "id": "fill-wrong-target",
      "title": "A noneditable paragraph rejects fill",
      "category": "Inputs",
      "html": "<p id=\"field\">unchanged</p>",
      "css": "",
      "setup": "",
      "description": "A noneditable paragraph rejects fill",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "bad"
          },
          "error": "not editable"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.textContent)})"
          },
          "expect": {
            "value": "unchanged"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-wrong-target.html"
    },
    {
      "id": "fill-form-partial",
      "title": "A form batch stops at its first failed field",
      "category": "Forms",
      "html": "<input id=\"first\"><p id=\"wrong\">text</p><input id=\"last\">",
      "css": "",
      "setup": "",
      "description": "A form batch stops at its first failed field",
      "steps": [
        {
          "method": "fill_form",
          "params": {
            "fields": [
              {
                "selector": "#first",
                "value": "saved"
              },
              {
                "selector": "#wrong",
                "value": "bad"
              },
              {
                "selector": "#last",
                "value": "later"
              }
            ]
          },
          "error": "not editable"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([first.value,last.value])})"
          },
          "expect": {
            "value": [
              "saved",
              ""
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fill-form-partial.html"
    },
    {
      "id": "form-invalid",
      "title": "Required validation prevents submission",
      "category": "Forms",
      "html": "<form id=\"form\"><input id=\"field\" name=\"q\" required><input name=\"token\" type=\"hidden\" value=\"fixture\"><button id=\"submit\">Submit</button><button id=\"reset\" type=\"reset\">Reset</button></form>",
      "css": "",
      "setup": "__lab.state.submits=0; form.addEventListener('submit',e=>{e.preventDefault();__lab.state.submits++;__lab.state.values=Object.fromEntries(new FormData(form))})",
      "description": "Required validation prevents submission",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#submit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.submits)})"
          },
          "expect": {
            "value": 0
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.validity.valueMissing)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/form-invalid.html"
    },
    {
      "id": "form-enter",
      "title": "Enter submits valid form once, including hidden fields",
      "category": "Forms",
      "html": "<form id=\"form\"><input id=\"field\" name=\"q\" required><input name=\"token\" type=\"hidden\" value=\"fixture\"><button id=\"submit\">Submit</button><button id=\"reset\" type=\"reset\">Reset</button></form>",
      "css": "",
      "setup": "__lab.state.submits=0; form.addEventListener('submit',e=>{e.preventDefault();__lab.state.submits++;__lab.state.values=Object.fromEntries(new FormData(form))})",
      "description": "Enter submits valid form once, including hidden fields",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "value",
            "submit": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.submits)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.values)})"
          },
          "expect": {
            "value": {
              "q": "value",
              "token": "fixture"
            }
          }
        },
        {
          "method": "title",
          "params": {},
          "verify": "result.includes('form-enter')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/form-enter.html"
    },
    {
      "id": "form-reset",
      "title": "Reset restores initial value and checked state",
      "category": "Forms",
      "html": "<form><input id=\"field\" value=\"default\"><input id=\"box\" type=\"checkbox\" checked><button id=\"reset\" type=\"reset\">Reset</button></form>",
      "css": "",
      "setup": "",
      "description": "Reset restores initial value and checked state",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "changed"
          }
        },
        {
          "method": "check",
          "params": {
            "selector": "#box",
            "checked": false
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#reset"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([field.value,box.checked])})"
          },
          "expect": {
            "value": [
              "default",
              true
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/form-reset.html"
    },
    {
      "id": "form-associated",
      "title": "An external form-associated input participates in submission",
      "category": "Forms",
      "html": "<form id=\"form\"><button id=\"submit\">Submit</button></form><input id=\"field\" form=\"form\" name=\"q\">",
      "css": "",
      "setup": "__lab.state.submits=0; form.addEventListener('submit',e=>{e.preventDefault();__lab.state.submits++;__lab.state.values=Object.fromEntries(new FormData(form))})",
      "description": "An external form-associated input participates in submission",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "external"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#submit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.values)})"
          },
          "expect": {
            "value": {
              "q": "external"
            }
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/form-associated.html"
    },
    {
      "id": "select-value",
      "title": "Select by value",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Alpha</option><option value=\"b\">Beta</option><option value=\"c\">Gamma</option></select>",
      "css": "",
      "setup": "",
      "description": "Select by value",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "b"
          },
          "expect": [
            "b"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "b"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-value.html"
    },
    {
      "id": "select-text",
      "title": "Select by text",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Alpha</option><option value=\"b\">Beta</option><option value=\"c\">Gamma</option></select>",
      "css": "",
      "setup": "",
      "description": "Select by text",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "Beta"
          },
          "expect": [
            "b"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "b"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-text.html"
    },
    {
      "id": "select-case",
      "title": "Select by case",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Alpha</option><option value=\"b\">Beta</option><option value=\"c\">Gamma</option></select>",
      "css": "",
      "setup": "",
      "description": "Select by case",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "c"
          },
          "expect": [
            "c"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "c"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-case.html"
    },
    {
      "id": "select-multi",
      "title": "Multiselect accepts a mixture of values and text",
      "category": "Selects",
      "html": "<select multiple id=\"choices\"><option value=\"a\">Alpha</option><option value=\"b\">Beta</option><option value=\"c\">Gamma</option></select>",
      "css": "",
      "setup": "",
      "description": "Multiselect accepts a mixture of values and text",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": [
              "a",
              "Gamma"
            ]
          },
          "expect": [
            "a",
            "c"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "a",
              "c"
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-multi.html"
    },
    {
      "id": "select-invalid-multi",
      "title": "Missing multiselect option leaves state and events untouched",
      "category": "Selects",
      "html": "<select multiple id=\"choices\"><option value=\"a\" selected>Alpha</option><option value=\"b\">Beta</option><option value=\"c\">Gamma</option></select>",
      "css": "",
      "setup": "",
      "description": "Missing multiselect option leaves state and events untouched",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": [
              "b",
              "missing"
            ]
          },
          "error": "no option matches"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "a"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['input','change'].includes(e.type)).length)})"
          },
          "expect": {
            "value": 0
          }
        },
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": [
              "b"
            ]
          },
          "expect": [
            "b"
          ]
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-invalid-multi.html"
    },
    {
      "id": "select-clear",
      "title": "Empty array clears a multiselect",
      "category": "Selects",
      "html": "<select multiple id=\"choices\"><option value=\"a\">Alpha</option><option value=\"b\">Beta</option><option value=\"c\">Gamma</option></select>",
      "css": "",
      "setup": "",
      "description": "Empty array clears a multiselect",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": [
              "a",
              "b"
            ]
          }
        },
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": []
          },
          "expect": []
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (choices.selectedOptions.length)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-clear.html"
    },
    {
      "id": "select-label-override",
      "title": "Option naming compatibility: label-override",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Initial</option><option value=\"b\" label=\"Visible label\">Source text</option></select>",
      "css": "",
      "setup": "",
      "description": "Option naming compatibility: label-override",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "Visible label"
          },
          "expect": [
            "b"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "b"
            ]
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-label-override.html"
    },
    {
      "id": "select-nbsp",
      "title": "Option naming compatibility: nbsp",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Initial</option><option value=\"b\">Alpha Beta</option></select>",
      "css": "",
      "setup": "",
      "description": "Option naming compatibility: nbsp",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "Alpha Beta"
          },
          "expect": [
            "b"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "b"
            ]
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-nbsp.html"
    },
    {
      "id": "select-zero-width",
      "title": "Option naming compatibility: zero-width",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Initial</option><option value=\"b\">Zero​Width</option></select>",
      "css": "",
      "setup": "",
      "description": "Option naming compatibility: zero-width",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "ZeroWidth"
          },
          "expect": [
            "b"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...choices.selectedOptions].map(o=>o.value))})"
          },
          "expect": {
            "value": [
              "b"
            ]
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-zero-width.html"
    },
    {
      "id": "select-case-sensitive",
      "title": "Option values remain case-sensitive",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"US\">Upper</option><option value=\"us\">Lower</option></select>",
      "css": "",
      "setup": "",
      "description": "Option values remain case-sensitive",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": "us"
          },
          "expect": [
            "us"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (choices.value)})"
          },
          "expect": {
            "value": "us"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-case-sensitive.html"
    },
    {
      "id": "select-empty-value",
      "title": "Empty-string option values are selectable",
      "category": "Selects",
      "html": "<select id=\"choices\"><option value=\"a\">Alpha</option><option value=\"\">Placeholder</option></select>",
      "css": "",
      "setup": "",
      "description": "Empty-string option values are selectable",
      "steps": [
        {
          "method": "select",
          "params": {
            "selector": "#choices",
            "value": ""
          },
          "expect": [
            ""
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (choices.value)})"
          },
          "expect": {
            "value": ""
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/select-empty-value.html"
    },
    {
      "id": "checkbox-idempotent",
      "title": "Check and uncheck are idempotent",
      "category": "Checkboxes",
      "html": "<input id=\"box\" type=\"checkbox\">",
      "css": "",
      "setup": "",
      "description": "Check and uncheck are idempotent",
      "steps": [
        {
          "method": "check",
          "params": {
            "selector": "#box"
          },
          "expect": true
        },
        {
          "method": "check",
          "params": {
            "selector": "#box"
          },
          "expect": true
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='change').length)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "check",
          "params": {
            "selector": "#box",
            "checked": false
          },
          "expect": false
        },
        {
          "method": "check",
          "params": {
            "selector": "#box",
            "checked": false
          },
          "expect": false
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='change').length)})"
          },
          "expect": {
            "value": 2
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/checkbox-idempotent.html"
    },
    {
      "id": "radio-group",
      "title": "Radio selection only changes its own group",
      "category": "Checkboxes",
      "html": "<input id=\"a\" name=\"g\" type=\"radio\" checked><input id=\"b\" name=\"g\" type=\"radio\"><input id=\"other\" name=\"h\" type=\"radio\" checked>",
      "css": "",
      "setup": "",
      "description": "Radio selection only changes its own group",
      "steps": [
        {
          "method": "check",
          "params": {
            "selector": "#b"
          },
          "expect": true
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([a.checked,b.checked,other.checked])})"
          },
          "expect": {
            "value": [
              false,
              true,
              true
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/radio-group.html"
    },
    {
      "id": "checkbox-cancelled",
      "title": "Canceled checkbox activation reports actual state",
      "category": "Checkboxes",
      "html": "<input id=\"box\" type=\"checkbox\">",
      "css": "",
      "setup": "box.onclick=e=>e.preventDefault()",
      "description": "Canceled checkbox activation reports actual state",
      "steps": [
        {
          "method": "check",
          "params": {
            "selector": "#box"
          },
          "expect": false
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (box.checked)})"
          },
          "expect": {
            "value": false
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/checkbox-cancelled.html"
    },
    {
      "id": "checkbox-hidden-label",
      "title": "Visible label activates its hidden native checkbox",
      "category": "Checkboxes",
      "html": "<label id=\"label\" for=\"box\">Accept</label><input id=\"box\" type=\"checkbox\" style=\"display:none\">",
      "css": "",
      "setup": "",
      "description": "Visible label activates its hidden native checkbox",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#label"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (box.checked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/checkbox-hidden-label.html"
    },
    {
      "id": "checkbox-aria-click",
      "title": "Clicking a custom ARIA checkbox changes application state",
      "category": "Checkboxes",
      "html": "<div id=\"box\" role=\"checkbox\" tabindex=\"0\" aria-checked=\"false\" onclick=\"this.setAttribute('aria-checked',String(this.getAttribute('aria-checked')!=='true'))\">Custom</div>",
      "css": "",
      "setup": "",
      "description": "Clicking a custom ARIA checkbox changes application state",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#box"
          }
        },
        {
          "method": "attr",
          "params": {
            "selector": "#box",
            "name": "aria-checked"
          },
          "expect": "true"
        },
        {
          "method": "check",
          "params": {
            "selector": "#box",
            "timeout": 200
          },
          "error": "not a checkbox"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/checkbox-aria-click.html"
    },
    {
      "id": "hit-display-none",
      "title": "Reject unreachable target: display-none",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{display:none}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Reject unreachable target: display-none",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-display-none.html"
    },
    {
      "id": "hit-hidden",
      "title": "Reject unreachable target: hidden",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{visibility:hidden}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Reject unreachable target: hidden",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-hidden.html"
    },
    {
      "id": "hit-ancestor-hidden",
      "title": "Reject unreachable target: ancestor-hidden",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:none}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Reject unreachable target: ancestor-hidden",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-ancestor-hidden.html"
    },
    {
      "id": "hit-outside",
      "title": "Reject unreachable target: outside",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{position:absolute;left:-9999px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Reject unreachable target: outside",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": "outside the viewport"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-outside.html"
    },
    {
      "id": "hit-pointer-none",
      "title": "Reject unreachable target: pointer-none",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{pointer-events:none}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Reject unreachable target: pointer-none",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": "covered"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-pointer-none.html"
    },
    {
      "id": "hit-opacity-zero",
      "title": "Activate target with opacity-zero",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{opacity:0}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Activate target with opacity-zero",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-opacity-zero.html"
    },
    {
      "id": "hit-transformed",
      "title": "Activate target with transformed",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{transform:translate(100px,80px) scale(1.4)}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Activate target with transformed",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-transformed.html"
    },
    {
      "id": "hit-below-fold",
      "title": "Activate target with below-fold",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{margin-top:1800px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Activate target with below-fold",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-below-fold.html"
    },
    {
      "id": "hit-contents",
      "title": "Activate target with contents",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:contents}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Activate target with contents",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-contents.html"
    },
    {
      "id": "hit-rtl",
      "title": "Activate target with rtl",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "body{direction:rtl}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Activate target with rtl",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-rtl.html"
    },
    {
      "id": "hit-vertical",
      "title": "Activate target with vertical",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{writing-mode:vertical-rl}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Activate target with vertical",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-vertical.html"
    },
    {
      "id": "layout-flex",
      "title": "Target identity in flex layout",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:flex;gap:16px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity in flex layout",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#neighbor"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        },
        {
          "method": "rect",
          "params": {
            "selector": "#neighbor"
          },
          "verify": "result.width>0 && result.height>0"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-flex.html"
    },
    {
      "id": "layout-grid",
      "title": "Target identity in grid layout",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:grid;gap:16px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity in grid layout",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#neighbor"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        },
        {
          "method": "rect",
          "params": {
            "selector": "#neighbor"
          },
          "verify": "result.width>0 && result.height>0"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-grid.html"
    },
    {
      "id": "layout-table",
      "title": "Target identity in table layout",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:table;gap:16px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity in table layout",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#neighbor"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        },
        {
          "method": "rect",
          "params": {
            "selector": "#neighbor"
          },
          "verify": "result.width>0 && result.height>0"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-table.html"
    },
    {
      "id": "layout-inline-block",
      "title": "Target identity in inline-block layout",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:inline-block;gap:16px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity in inline-block layout",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#neighbor"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        },
        {
          "method": "rect",
          "params": {
            "selector": "#neighbor"
          },
          "verify": "result.width>0 && result.height>0"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-inline-block.html"
    },
    {
      "id": "layout-flow-root",
      "title": "Target identity in flow-root layout",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{display:flow-root;gap:16px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity in flow-root layout",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#neighbor"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              1
            ]
          }
        },
        {
          "method": "rect",
          "params": {
            "selector": "#neighbor"
          },
          "verify": "result.width>0 && result.height>0"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-flow-root.html"
    },
    {
      "id": "layout-float",
      "title": "Target identity with float",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{float:right}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity with float",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-float.html"
    },
    {
      "id": "layout-columns",
      "title": "Target identity with columns",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{columns:2}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity with columns",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-columns.html"
    },
    {
      "id": "layout-logical",
      "title": "Target identity with logical",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "#target{margin-inline-start:80px;padding-block:20px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity with logical",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-logical.html"
    },
    {
      "id": "layout-custom-property",
      "title": "Target identity with custom-property",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": ":root{--gap:40px}#target{margin-left:var(--gap)}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity with custom-property",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-custom-property.html"
    },
    {
      "id": "layout-content-visibility",
      "title": "Target identity with content-visibility",
      "category": "CSS layout",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{content-visibility:auto;contain-intrinsic-size:200px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Target identity with content-visibility",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/layout-content-visibility.html"
    },
    {
      "id": "hit-overlay",
      "title": "Transparent pointer-active overlay blocks the target",
      "category": "Hit testing",
      "html": "<button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button><div id=\"overlay\"></div>",
      "css": "#overlay{position:fixed;inset:0;background:transparent;z-index:10}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Transparent pointer-active overlay blocks the target",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": "covered"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              0,
              0
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "overlay.remove()"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-overlay.html"
    },
    {
      "id": "hit-overlay-pass-through",
      "title": "Pointer-inactive overlay allows the target click",
      "category": "Hit testing",
      "html": "<button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button><div id=\"overlay\"></div>",
      "css": "#overlay{position:fixed;inset:0;background:transparent;z-index:10}#overlay{pointer-events:none}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Pointer-inactive overlay allows the target click",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-overlay-pass-through.html"
    },
    {
      "id": "hit-force",
      "title": "Force explicitly bypasses an overlay",
      "category": "Hit testing",
      "html": "<button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button><div id=\"overlay\"></div>",
      "css": "#overlay{position:fixed;inset:0;background:transparent;z-index:10}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Force explicitly bypasses an overlay",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "force": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-force.html"
    },
    {
      "id": "hit-overlay-release",
      "title": "Waiting click completes once an overlay is released",
      "category": "Hit testing",
      "html": "<button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button><div id=\"overlay\"></div>",
      "css": "#overlay{position:fixed;inset:0;background:transparent;z-index:10}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Waiting click completes once an overlay is released",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 4000
          },
          "send": "pending"
        },
        {
          "method": "eval",
          "params": {
            "expression": "overlay.remove()"
          }
        },
        {
          "await": "pending"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-overlay-release.html"
    },
    {
      "id": "hit-nested-scroll",
      "title": "Nested containers scroll to the intended target",
      "category": "Hit testing",
      "html": "<div id=\"outer\"><div class=\"spacer\"></div><div id=\"inner\"><div class=\"spacer\"></div><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></div></div>",
      "css": "#outer,#inner{height:180px;overflow:auto;border:1px solid} .spacer{height:1000px}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Nested containers scroll to the intended target",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (outer.scrollTop>0 && inner.scrollTop>0)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-nested-scroll.html"
    },
    {
      "id": "hit-hidden-override",
      "title": "CSS can override the hidden attribute rendering",
      "category": "Hit testing",
      "html": "<button id=\"target\" hidden onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button>",
      "css": "#target{display:inline-block}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "CSS can override the hidden attribute rendering",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-hidden-override.html"
    },
    {
      "id": "hit-details",
      "title": "Opening details reveals its control",
      "category": "Hit testing",
      "html": "<details><summary id=\"summary\">Expand</summary><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></details>",
      "css": "",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Opening details reveals its control",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": ""
        },
        {
          "method": "click",
          "params": {
            "selector": "#summary"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-details.html"
    },
    {
      "id": "hit-visibility-override",
      "title": "A descendant can override inherited hidden visibility",
      "category": "Hit testing",
      "html": "<section><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "section{visibility:hidden}#target{visibility:visible}",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "A descendant can override inherited hidden visibility",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.name==='Target') && !result.elements.some(e=>e.name==='Neighbor')"
        },
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([__lab.state.clicks,__lab.state.neighbor])})"
          },
          "expect": {
            "value": [
              1,
              0
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-visibility-override.html"
    },
    {
      "id": "hit-shadow-descendant",
      "title": "A shadow descendant receives hits inside its button",
      "category": "Hit testing",
      "html": "<button id=\"target\" onclick=\"__lab.state.clicks++\"><span id=\"host\"></span></button>",
      "css": "",
      "setup": "__lab.state={clicks:0,neighbor:0};host.attachShadow({mode:'open'}).innerHTML='<span style=display:block;padding:30px>Inside</span>'",
      "description": "A shadow descendant receives hits inside its button",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-shadow-descendant.html"
    },
    {
      "id": "hit-inert",
      "title": "Inert background rejects interactive actions",
      "category": "Hit testing",
      "html": "<section inert><button id=\"target\" onclick=\"__lab.state.clicks++\">Target</button><button id=\"neighbor\" onclick=\"__lab.state.neighbor++\">Neighbor</button></section>",
      "css": "",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "Inert background rejects interactive actions",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target",
            "timeout": 150
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-inert.html"
    },
    {
      "id": "hit-coordinate",
      "title": "A coordinate click on an icon bubbles to its button once",
      "category": "Hit testing",
      "html": "<button id=\"target\" onclick=\"__lab.state.clicks++\"><span id=\"icon\">Icon</span></button>",
      "css": "",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "A coordinate click on an icon bubbles to its button once",
      "steps": [
        {
          "method": "rect",
          "params": {
            "selector": "#icon"
          },
          "capture": {
            "x": "result.x+result.width/2",
            "y": "result.y+result.height/2"
          }
        },
        {
          "method": "click",
          "params": {
            "x": "{{x}}",
            "y": "{{y}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='click')[0].target)})"
          },
          "expect": {
            "value": "icon"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-coordinate.html"
    },
    {
      "id": "hit-svg-stroke",
      "title": "SVG stroke remains interactive with zero geometric height",
      "category": "Hit testing",
      "html": "<svg width=\"400\" height=\"100\"><line id=\"line\" x1=\"20\" y1=\"50\" x2=\"380\" y2=\"50\" stroke=\"red\" stroke-width=\"12\" onclick=\"__lab.state.clicks++\"/></svg>",
      "css": "",
      "setup": "__lab.state={clicks:0,neighbor:0};",
      "description": "SVG stroke remains interactive with zero geometric height",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#line"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hit-svg-stroke.html"
    },
    {
      "id": "event-click-order",
      "title": "Click produces one activation and ordered mouse events",
      "category": "Events",
      "html": "<button id=\"target\">Click</button>",
      "css": "",
      "setup": "",
      "description": "Click produces one activation and ordered mouse events",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.target==='target'&&['mousedown','mouseup','click'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "mousedown",
              "mouseup",
              "click"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>e.type==='click').every(e=>e.trusted===false))})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/event-click-order.html"
    },
    {
      "id": "event-propagation",
      "title": "Capture, target, and bubble listeners fire in order",
      "category": "Events",
      "html": "<section id=\"container\"><button id=\"target\">Click</button></section>",
      "css": "",
      "setup": "__lab.state.order=[];container.addEventListener('click',()=>__lab.state.order.push('capture'),true);target.onclick=()=>__lab.state.order.push('target');container.addEventListener('click',()=>__lab.state.order.push('bubble'));",
      "description": "Capture, target, and bubble listeners fire in order",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.order)})"
          },
          "expect": {
            "value": [
              "capture",
              "target",
              "bubble"
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/event-propagation.html"
    },
    {
      "id": "event-stop-propagation",
      "title": "Stopped propagation does not activate the parent",
      "category": "Events",
      "html": "<section id=\"container\"><button id=\"target\">Click</button></section>",
      "css": "",
      "setup": "__lab.state.parent=0;container.onclick=()=>__lab.state.parent++;target.onclick=e=>e.stopPropagation()",
      "description": "Stopped propagation does not activate the parent",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.parent)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/event-stop-propagation.html"
    },
    {
      "id": "key-control-k",
      "title": "Keyboard modifiers: Control+k",
      "category": "Keyboard",
      "html": "<input id=\"field\">",
      "css": "",
      "setup": "field.onkeydown=e=>{__lab.state.key=[e.key,e.ctrlKey,e.shiftKey,e.altKey,e.metaKey];e.preventDefault()}",
      "description": "Keyboard modifiers: Control+k",
      "steps": [
        {
          "method": "press",
          "params": {
            "selector": "#field",
            "key": "Control+k"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.key)})"
          },
          "expect": {
            "value": [
              "k",
              true,
              false,
              false,
              false
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-control-k.html"
    },
    {
      "id": "key-shift-a",
      "title": "Keyboard modifiers: Shift+A",
      "category": "Keyboard",
      "html": "<input id=\"field\">",
      "css": "",
      "setup": "field.onkeydown=e=>{__lab.state.key=[e.key,e.ctrlKey,e.shiftKey,e.altKey,e.metaKey];e.preventDefault()}",
      "description": "Keyboard modifiers: Shift+A",
      "steps": [
        {
          "method": "press",
          "params": {
            "selector": "#field",
            "key": "Shift+A"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.key)})"
          },
          "expect": {
            "value": [
              "A",
              false,
              true,
              false,
              false
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-shift-a.html"
    },
    {
      "id": "key-alt-x",
      "title": "Keyboard modifiers: Alt+x",
      "category": "Keyboard",
      "html": "<input id=\"field\">",
      "css": "",
      "setup": "field.onkeydown=e=>{__lab.state.key=[e.key,e.ctrlKey,e.shiftKey,e.altKey,e.metaKey];e.preventDefault()}",
      "description": "Keyboard modifiers: Alt+x",
      "steps": [
        {
          "method": "press",
          "params": {
            "selector": "#field",
            "key": "Alt+x"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.key)})"
          },
          "expect": {
            "value": [
              "x",
              false,
              false,
              true,
              false
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-alt-x.html"
    },
    {
      "id": "key-meta-k",
      "title": "Keyboard modifiers: Meta+k",
      "category": "Keyboard",
      "html": "<input id=\"field\">",
      "css": "",
      "setup": "field.onkeydown=e=>{__lab.state.key=[e.key,e.ctrlKey,e.shiftKey,e.altKey,e.metaKey];e.preventDefault()}",
      "description": "Keyboard modifiers: Meta+k",
      "steps": [
        {
          "method": "press",
          "params": {
            "selector": "#field",
            "key": "Meta+k"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.key)})"
          },
          "expect": {
            "value": [
              "k",
              false,
              false,
              false,
              true
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-meta-k.html"
    },
    {
      "id": "key-cancelled-press",
      "title": "Canceled printable press does not insert text",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start\">",
      "css": "",
      "setup": "field.onkeydown=e=>e.preventDefault()",
      "description": "Canceled printable press does not insert text",
      "steps": [
        {
          "method": "press",
          "params": {
            "selector": "#field",
            "key": "x"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-cancelled-press.html"
    },
    {
      "id": "key-cancelled-type",
      "title": "Canceled keydown should suppress typed insertion",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"start\">",
      "css": "",
      "setup": "field.onkeydown=e=>e.preventDefault()",
      "description": "Canceled keydown should suppress typed insertion",
      "steps": [
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "x"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "start"
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-cancelled-type.html"
    },
    {
      "id": "key-caret",
      "title": "Typing with a middle caret follows native editing semantics",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"ac\">",
      "css": "",
      "setup": "",
      "description": "Typing with a middle caret follows native editing semantics",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "field.focus();field.setSelectionRange(1,1)"
          }
        },
        {
          "method": "type",
          "params": {
            "selector": "#field",
            "text": "b"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "abc"
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-caret.html"
    },
    {
      "id": "key-backspace",
      "title": "Backspace removes the preceding character",
      "category": "Keyboard",
      "html": "<input id=\"field\" value=\"abc\">",
      "css": "",
      "setup": "",
      "description": "Backspace removes the preceding character",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "field.focus();field.setSelectionRange(3,3)"
          }
        },
        {
          "method": "press",
          "params": {
            "key": "Backspace"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "ab"
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/key-backspace.html"
    },
    {
      "id": "hover-js",
      "title": "Synthetic hover opens a JavaScript menu",
      "category": "Events",
      "html": "<button id=\"target\">Hover</button><div id=\"menu\" hidden>Menu</div>",
      "css": "",
      "setup": "target.onmouseenter=()=>menu.hidden=false",
      "description": "Synthetic hover opens a JavaScript menu",
      "steps": [
        {
          "method": "hover",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (menu.hidden)})"
          },
          "expect": {
            "value": false
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hover-js.html"
    },
    {
      "id": "hover-css",
      "title": "Synthetic hover leaves native hover state unchanged",
      "category": "Events",
      "html": "<button id=\"target\">Hover</button>",
      "css": "",
      "setup": "",
      "description": "Bruster dispatches DOM hover events; it does not move the OS pointer or emulate CSS :hover.",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "__lab.state.before=target.matches(':hover')"
          }
        },
        {
          "method": "hover",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (target.matches(':hover')===__lab.state.before)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/hover-css.html"
    },
    {
      "id": "drag-html5",
      "title": "HTML5 drag transfers one exact payload",
      "category": "Drag and drop",
      "html": "<div id=\"source\" draggable=\"true\">Source</div><div id=\"dest\">Destination</div>",
      "css": "#source,#dest{display:inline-block;margin:30px;padding:30px;border:2px solid}",
      "setup": "__lab.state.drops=0;source.ondragstart=e=>e.dataTransfer.setData('text/plain','payload 🎉');dest.ondragover=e=>e.preventDefault();dest.ondrop=e=>{__lab.state.drops++;__lab.state.payload=e.dataTransfer.getData('text/plain')}",
      "description": "HTML5 drag transfers one exact payload",
      "steps": [
        {
          "method": "drag",
          "params": {
            "from": {
              "selector": "#source"
            },
            "to": {
              "selector": "#dest"
            }
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.drops)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.payload)})"
          },
          "expect": {
            "value": "payload 🎉"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['dragstart','dragenter','dragover','drop','dragend'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "dragstart",
              "dragenter",
              "dragover",
              "drop",
              "dragend"
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/drag-html5.html"
    },
    {
      "id": "drag-cancelled",
      "title": "Canceling dragstart should prevent the drop",
      "category": "Drag and drop",
      "html": "<div id=\"source\" draggable=\"true\">Source</div><div id=\"dest\">Destination</div>",
      "css": "",
      "setup": "__lab.state.drops=0;source.ondragstart=e=>e.dataTransfer.setData('text/plain','payload 🎉');dest.ondragover=e=>e.preventDefault();dest.ondrop=e=>{__lab.state.drops++;__lab.state.payload=e.dataTransfer.getData('text/plain')};source.ondragstart=e=>e.preventDefault()",
      "description": "Canceling dragstart should prevent the drop",
      "steps": [
        {
          "method": "drag",
          "params": {
            "from": {
              "selector": "#source"
            },
            "to": {
              "selector": "#dest"
            }
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.drops)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/drag-cancelled.html"
    },
    {
      "id": "drag-pointer-slider",
      "title": "Pointer-driven dragging changes actual slider position",
      "category": "Drag and drop",
      "html": "<div id=\"source\" draggable=\"true\">Source</div><div id=\"dest\">Destination</div>",
      "css": "",
      "setup": "__lab.state.moved=false;source.onpointerdown=e=>source.setPointerCapture(e.pointerId);source.onpointermove=e=>{if(e.buttons===1)__lab.state.moved=true}",
      "description": "Capability probe for native pointer capture and movement; Bruster currently promises HTML5 drag events.",
      "steps": [
        {
          "method": "drag",
          "params": {
            "from": {
              "selector": "#source"
            },
            "to": {
              "selector": "#dest"
            }
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.moved)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/drag-pointer-slider.html"
    },
    {
      "id": "upload-single",
      "title": "Upload single",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\">",
      "css": "",
      "setup": "",
      "description": "Upload single",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{textFile}}"
            ]
          },
          "expect": [
            "sample.txt"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...file.files].map(f=>f.size))})"
          },
          "expect": {
            "value": [
              6
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['input','change'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "input",
              "change"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (await file.files[0].text())})"
          },
          "expect": {
            "value": "hello\n"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-single.html"
    },
    {
      "id": "upload-multiple",
      "title": "Upload multiple",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\" multiple>",
      "css": "",
      "setup": "",
      "description": "Upload multiple",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{textFile}}",
              "{{binaryFile}}"
            ]
          },
          "expect": [
            "sample.txt",
            "bytes.bin"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...file.files].map(f=>f.size))})"
          },
          "expect": {
            "value": [
              6,
              256
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['input','change'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "input",
              "change"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (await file.files[0].text())})"
          },
          "expect": {
            "value": "hello\n"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-multiple.html"
    },
    {
      "id": "upload-hidden",
      "title": "Upload hidden",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\" hidden>",
      "css": "",
      "setup": "",
      "description": "Upload hidden",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{textFile}}"
            ]
          },
          "expect": [
            "sample.txt"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...file.files].map(f=>f.size))})"
          },
          "expect": {
            "value": [
              6
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['input','change'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "input",
              "change"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (await file.files[0].text())})"
          },
          "expect": {
            "value": "hello\n"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-hidden.html"
    },
    {
      "id": "upload-empty-file",
      "title": "Upload empty-file",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\">",
      "css": "",
      "setup": "",
      "description": "Upload empty-file",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{emptyFile}}"
            ]
          },
          "expect": [
            "empty.txt"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...file.files].map(f=>f.size))})"
          },
          "expect": {
            "value": [
              0
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['input','change'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "input",
              "change"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (await file.files[0].text())})"
          },
          "expect": {
            "value": ""
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-empty-file.html"
    },
    {
      "id": "upload-unicode-name",
      "title": "Upload unicode-name",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\">",
      "css": "",
      "setup": "",
      "description": "Upload unicode-name",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{unicodeFile}}"
            ]
          },
          "expect": [
            "résumé-日本.txt"
          ]
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: ([...file.files].map(f=>f.size))})"
          },
          "expect": {
            "value": [
              6
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.events.filter(e=>['input','change'].includes(e.type)).map(e=>e.type))})"
          },
          "expect": {
            "value": [
              "input",
              "change"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (await file.files[0].text())})"
          },
          "expect": {
            "value": "hello\n"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-unicode-name.html"
    },
    {
      "id": "upload-binary",
      "title": "Uploaded binary contents are byte-for-byte intact",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\">",
      "css": "",
      "setup": "",
      "description": "Uploaded binary contents are byte-for-byte intact",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{binaryFile}}"
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (Array.from(new Uint8Array(await file.files[0].arrayBuffer())))})"
          },
          "expect": {
            "value": [
              0,
              1,
              2,
              3,
              4,
              5,
              6,
              7,
              8,
              9,
              10,
              11,
              12,
              13,
              14,
              15,
              16,
              17,
              18,
              19,
              20,
              21,
              22,
              23,
              24,
              25,
              26,
              27,
              28,
              29,
              30,
              31,
              32,
              33,
              34,
              35,
              36,
              37,
              38,
              39,
              40,
              41,
              42,
              43,
              44,
              45,
              46,
              47,
              48,
              49,
              50,
              51,
              52,
              53,
              54,
              55,
              56,
              57,
              58,
              59,
              60,
              61,
              62,
              63,
              64,
              65,
              66,
              67,
              68,
              69,
              70,
              71,
              72,
              73,
              74,
              75,
              76,
              77,
              78,
              79,
              80,
              81,
              82,
              83,
              84,
              85,
              86,
              87,
              88,
              89,
              90,
              91,
              92,
              93,
              94,
              95,
              96,
              97,
              98,
              99,
              100,
              101,
              102,
              103,
              104,
              105,
              106,
              107,
              108,
              109,
              110,
              111,
              112,
              113,
              114,
              115,
              116,
              117,
              118,
              119,
              120,
              121,
              122,
              123,
              124,
              125,
              126,
              127,
              128,
              129,
              130,
              131,
              132,
              133,
              134,
              135,
              136,
              137,
              138,
              139,
              140,
              141,
              142,
              143,
              144,
              145,
              146,
              147,
              148,
              149,
              150,
              151,
              152,
              153,
              154,
              155,
              156,
              157,
              158,
              159,
              160,
              161,
              162,
              163,
              164,
              165,
              166,
              167,
              168,
              169,
              170,
              171,
              172,
              173,
              174,
              175,
              176,
              177,
              178,
              179,
              180,
              181,
              182,
              183,
              184,
              185,
              186,
              187,
              188,
              189,
              190,
              191,
              192,
              193,
              194,
              195,
              196,
              197,
              198,
              199,
              200,
              201,
              202,
              203,
              204,
              205,
              206,
              207,
              208,
              209,
              210,
              211,
              212,
              213,
              214,
              215,
              216,
              217,
              218,
              219,
              220,
              221,
              222,
              223,
              224,
              225,
              226,
              227,
              228,
              229,
              230,
              231,
              232,
              233,
              234,
              235,
              236,
              237,
              238,
              239,
              240,
              241,
              242,
              243,
              244,
              245,
              246,
              247,
              248,
              249,
              250,
              251,
              252,
              253,
              254,
              255
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-binary.html"
    },
    {
      "id": "upload-clear",
      "title": "Upload replaces and clears the file list",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\" multiple>",
      "css": "",
      "setup": "",
      "description": "Upload replaces and clears the file list",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{textFile}}",
              "{{binaryFile}}"
            ]
          }
        },
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{emptyFile}}"
            ]
          },
          "expect": [
            "empty.txt"
          ]
        },
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": []
          },
          "expect": []
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (file.files.length)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-clear.html"
    },
    {
      "id": "upload-missing",
      "title": "Missing files report errors without losing the previous upload",
      "category": "Uploads",
      "html": "<input id=\"file\" type=\"file\">",
      "css": "",
      "setup": "",
      "description": "Missing files report errors without losing the previous upload",
      "steps": [
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{textFile}}"
            ]
          }
        },
        {
          "method": "upload",
          "params": {
            "selector": "#file",
            "paths": [
              "{{missingFile}}"
            ]
          },
          "error": "upload"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (file.files[0].name)})"
          },
          "expect": {
            "value": "sample.txt"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/upload-missing.html"
    },
    {
      "id": "shadow-open",
      "title": "Selectors and refs reach open shadow roots",
      "category": "Composed trees",
      "html": "<div id=\"host\"></div>",
      "css": "",
      "setup": "__lab.state.clicks=0;host.attachShadow({mode:'open'}).innerHTML='<button id=inside onclick=\"window.__lab.state.clicks++\">Inside</button>'",
      "description": "Selectors and refs reach open shadow roots",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "capture": {
            "ref": "result.elements.find(e=>e.name==='Inside').ref"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{ref}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicks)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "count",
          "params": {
            "selector": "#inside"
          },
          "expect": 1
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-open.html"
    },
    {
      "id": "shadow-scope",
      "title": "Host-scoped snapshot includes its own shadow root",
      "category": "Composed trees",
      "html": "<div id=\"host\"></div><button>Outside</button>",
      "css": "",
      "setup": "host.attachShadow({mode:'open'}).innerHTML='<button>Inside</button>'",
      "description": "Host-scoped snapshot includes its own shadow root",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "selector": "#host",
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.name==='Inside') && !result.elements.some(e=>e.name==='Outside')"
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-scope.html"
    },
    {
      "id": "shadow-depth-1",
      "title": "Traverse 1 nested open shadow roots",
      "category": "Composed trees",
      "html": "<div id=\"host\"></div>",
      "css": "",
      "setup": "let h=host;for(let i=0;i<1;i++){const r=h.attachShadow({mode:'open'});r.innerHTML='<div></div>';h=r.firstChild}h.innerHTML='<button id=deep onclick=\"window.__lab.state.clicked=true\">Deep</button>'",
      "description": "Traverse 1 nested open shadow roots",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#deep"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-depth-1.html"
    },
    {
      "id": "shadow-depth-5",
      "title": "Traverse 5 nested open shadow roots",
      "category": "Composed trees",
      "html": "<div id=\"host\"></div>",
      "css": "",
      "setup": "let h=host;for(let i=0;i<5;i++){const r=h.attachShadow({mode:'open'});r.innerHTML='<div></div>';h=r.firstChild}h.innerHTML='<button id=deep onclick=\"window.__lab.state.clicked=true\">Deep</button>'",
      "description": "Traverse 5 nested open shadow roots",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#deep"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-depth-5.html"
    },
    {
      "id": "shadow-depth-20",
      "title": "Traverse 20 nested open shadow roots",
      "category": "Composed trees",
      "html": "<div id=\"host\"></div>",
      "css": "",
      "setup": "let h=host;for(let i=0;i<20;i++){const r=h.attachShadow({mode:'open'});r.innerHTML='<div></div>';h=r.firstChild}h.innerHTML='<button id=deep onclick=\"window.__lab.state.clicked=true\">Deep</button>'",
      "description": "Traverse 20 nested open shadow roots",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#deep"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-depth-20.html"
    },
    {
      "id": "shadow-closed",
      "title": "Closed shadow contents stay outside selector traversal",
      "category": "Composed trees",
      "html": "<div id=\"host\"></div><button id=\"outside\">Outside</button>",
      "css": "",
      "setup": "host.attachShadow({mode:'closed'}).innerHTML='<button id=closed>Closed</button>'",
      "description": "Closed shadow contents stay outside selector traversal",
      "steps": [
        {
          "method": "count",
          "params": {
            "selector": "#closed"
          },
          "expect": 0
        },
        {
          "method": "click",
          "params": {
            "selector": "#outside"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-closed.html"
    },
    {
      "id": "shadow-slots",
      "title": "Slotted controls remain actionable without duplicate discovery",
      "category": "Composed trees",
      "html": "<div id=\"host\"><button id=\"slotted\" onclick=\"__lab.state.clicked=true\">Slotted</button></div>",
      "css": "",
      "setup": "host.attachShadow({mode:'open'}).innerHTML='<section><slot></slot></section>'",
      "description": "Slotted controls remain actionable without duplicate discovery",
      "steps": [
        {
          "method": "count",
          "params": {
            "selector": "#slotted"
          },
          "expect": 1
        },
        {
          "method": "click",
          "params": {
            "selector": "#slotted"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/shadow-slots.html"
    },
    {
      "id": "frame-same-origin",
      "title": "Same-origin frame controls work through selector and reference",
      "category": "Composed trees",
      "html": "<iframe id=\"frame\" title=\"Fixture frame\" src=\"../support/frame.html\"></iframe>",
      "css": "",
      "setup": "",
      "description": "Same-origin frame controls work through selector and reference",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise(resolve=>{if(frame.contentDocument?.readyState==='complete'&&frame.contentDocument.getElementById('inside'))resolve();else frame.addEventListener('load',resolve,{once:true})})"
          }
        },
        {
          "method": "count",
          "params": {
            "selector": "#inside"
          },
          "expect": 1
        },
        {
          "method": "click",
          "params": {
            "selector": "#inside"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (frame.contentWindow.clicks)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "capture": {
            "ref": "result.elements.find(e=>e.name==='Frame button').ref"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{ref}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (frame.contentWindow.clicks)})"
          },
          "expect": {
            "value": 2
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/frame-same-origin.html"
    },
    {
      "id": "frame-replaced-ref",
      "title": "Frame replacement invalidates child references",
      "category": "Composed trees",
      "html": "<iframe id=\"frame\" title=\"Fixture frame\" src=\"../support/frame.html\"></iframe>",
      "css": "",
      "setup": "",
      "description": "Frame replacement invalidates child references",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise(resolve=>{if(frame.contentDocument?.readyState==='complete'&&frame.contentDocument.getElementById('inside'))resolve();else frame.addEventListener('load',resolve,{once:true})})"
          }
        },
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "capture": {
            "ref": "result.elements.find(e=>e.name==='Frame button').ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise(resolve=>{frame.onload=resolve;frame.src='../support/frame.html?second'})"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{ref}}",
            "timeout": 150
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "text",
          "params": {
            "ref": "{{ref}}"
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "wait_for",
          "params": {
            "ref": "{{ref}}",
            "state": "hidden",
            "timeout": 150
          }
        },
        {
          "method": "wait_for",
          "params": {
            "ref": "{{ref}}",
            "state": "attached",
            "timeout": 150
          },
          "error": "wait_for timed out"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (frame.contentWindow.clicks)})"
          },
          "expect": {
            "value": 0
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#inside"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (frame.contentWindow.clicks)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/frame-replaced-ref.html"
    },
    {
      "id": "frame-removed-ref",
      "title": "Removing a frame invalidates references inside its document",
      "category": "Composed trees",
      "html": "<iframe id=\"frame\" title=\"Fixture frame\" src=\"../support/frame.html\"></iframe>",
      "css": "",
      "setup": "",
      "description": "Removing a frame invalidates references inside its document",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise(resolve=>{if(frame.contentDocument?.readyState==='complete'&&frame.contentDocument.getElementById('inside'))resolve();else frame.addEventListener('load',resolve,{once:true})})"
          }
        },
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "capture": {
            "ref": "result.elements.find(e=>e.name==='Frame button').ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "frame.remove()"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{ref}}",
            "timeout": 150
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "text",
          "params": {
            "ref": "{{ref}}"
          },
          "error": "stale or unknown ref"
        },
        {
          "method": "wait_for",
          "params": {
            "ref": "{{ref}}",
            "state": "hidden",
            "timeout": 150
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.querySelectorAll('iframe').length)})"
          },
          "expect": {
            "value": 0
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/frame-removed-ref.html"
    },
    {
      "id": "frame-offset-rect",
      "title": "Frame descendant rectangles use the top-level viewport",
      "category": "Composed trees",
      "html": "<iframe id=\"frame\" title=\"Fixture frame\" src=\"../support/frame.html\"></iframe>",
      "css": "#frame{margin:120px;border:4px solid;width:400px;height:250px}",
      "setup": "",
      "description": "Frame descendant rectangles use the top-level viewport",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise(resolve=>{if(frame.contentDocument?.readyState==='complete'&&frame.contentDocument.getElementById('inside'))resolve();else frame.addEventListener('load',resolve,{once:true})})"
          }
        },
        {
          "method": "rect",
          "params": {
            "selector": "#inside"
          },
          "verify": "Math.abs(result.x-(frame.getBoundingClientRect().x+frame.clientLeft+frame.contentDocument.getElementById('inside').getBoundingClientRect().x))<=1"
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/frame-offset-rect.html"
    },
    {
      "id": "frame-cross-origin",
      "title": "Cross-origin child is inaccessible while parent remains usable",
      "category": "Composed trees",
      "html": "<iframe id=\"frame\" title=\"Cross-origin frame\"></iframe><button id=\"parentButton\">Parent</button>",
      "css": "",
      "setup": "await new Promise(resolve=>{frame.onload=resolve;frame.src=__lab.server.otherOrigin+__lab.sitePath+'support/frame.html'})",
      "description": "Cross-origin child is inaccessible while parent remains usable",
      "steps": [
        {
          "method": "count",
          "params": {
            "selector": "#inside"
          },
          "expect": 0
        },
        {
          "method": "click",
          "params": {
            "selector": "#parentButton"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (frame.contentDocument)})"
          },
          "expect": {
            "value": null
          }
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/frame-cross-origin.html"
    },
    {
      "id": "frame-sandbox",
      "title": "Opaque sandbox frame does not leak into traversal",
      "category": "Composed trees",
      "html": "<iframe sandbox=\"allow-scripts\" title=\"Sandbox\" srcdoc=\"<button id=opaque>Opaque</button>\"></iframe>",
      "css": "",
      "setup": "",
      "description": "Opaque sandbox frame does not leak into traversal",
      "steps": [
        {
          "method": "count",
          "params": {
            "selector": "#opaque"
          },
          "expect": 0
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/frame-sandbox.html"
    },
    {
      "id": "responsive-480",
      "title": "Responsive breakpoint at viewport 480",
      "category": "Rendering",
      "html": "<div id=\"box\">Responsive</div>",
      "css": "#box{color:blue}@media(max-width:599px){#box{color:red}}",
      "setup": "",
      "description": "Responsive breakpoint at viewport 480",
      "steps": [
        {
          "method": "resize",
          "params": {
            "width": 480,
            "height": 640
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>innerWidth===480 && innerHeight===640);await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)))"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (innerWidth)})"
          },
          "expect": {
            "value": 480
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (getComputedStyle(box).color)})"
          },
          "expect": {
            "value": "rgb(255, 0, 0)"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--visible"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/responsive-480.html"
    },
    {
      "id": "responsive-960",
      "title": "Responsive breakpoint at viewport 960",
      "category": "Rendering",
      "html": "<div id=\"box\">Responsive</div>",
      "css": "#box{color:blue}@media(max-width:599px){#box{color:red}}",
      "setup": "",
      "description": "Responsive breakpoint at viewport 960",
      "steps": [
        {
          "method": "resize",
          "params": {
            "width": 960,
            "height": 640
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>innerWidth===960 && innerHeight===640);await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)))"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (innerWidth)})"
          },
          "expect": {
            "value": 960
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (getComputedStyle(box).color)})"
          },
          "expect": {
            "value": "rgb(0, 0, 255)"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--visible"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/responsive-960.html"
    },
    {
      "id": "container-query",
      "title": "Container queries respond to their container width",
      "category": "Rendering",
      "html": "<section id=\"container\"><span id=\"child\">Content</span></section>",
      "css": "#container{container-type:inline-size;width:200px}#child{color:blue}@container(max-width:250px){#child{color:red}}",
      "setup": "",
      "description": "Container queries respond to their container width",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "({value: (getComputedStyle(child).color)})"
          },
          "expect": {
            "value": "rgb(255, 0, 0)"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "container.style.width='400px';await new Promise(r=>requestAnimationFrame(r))"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (getComputedStyle(child).color)})"
          },
          "expect": {
            "value": "rgb(0, 0, 255)"
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "CSS.supports('container-type','inline-size')",
          "reason": "Container queries unsupported"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--visible"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/container-query.html"
    },
    {
      "id": "screenshot-quadrants",
      "title": "Screenshot pixels contain the current viewport content",
      "category": "Screenshots",
      "html": "<div id=\"red\"></div><div id=\"green\"></div><div id=\"blue\"></div><div id=\"white\"></div>",
      "css": "body{margin:0;display:grid;grid-template-columns:200px 200px}body>div{width:200px;height:200px}#red{background:red}#green{background:green}#blue{background:blue}#white{background:white}",
      "setup": "",
      "description": "Screenshot pixels contain the current viewport content",
      "steps": [
        {
          "method": "screenshot",
          "params": {},
          "image": {
            "samples": [
              [
                50,
                50,
                [
                  255,
                  0,
                  0
                ]
              ],
              [
                250,
                50,
                [
                  0,
                  128,
                  0
                ]
              ],
              [
                50,
                250,
                [
                  0,
                  0,
                  255
                ]
              ],
              [
                250,
                250,
                [
                  255,
                  255,
                  255
                ]
              ]
            ],
            "minWidth": 400,
            "minHeight": 400
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/screenshot-quadrants.html"
    },
    {
      "id": "screenshot-canvas-update",
      "title": "Native capture reflects a canvas update",
      "category": "Screenshots",
      "html": "<canvas id=\"canvas\" width=\"300\" height=\"300\"></canvas>",
      "css": "body{margin:0}",
      "setup": "",
      "description": "Native capture reflects a canvas update",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "canvas.getContext('2d').fillStyle='red';canvas.getContext('2d').fillRect(0,0,300,300);await new Promise(r=>requestAnimationFrame(r))"
          }
        },
        {
          "method": "screenshot",
          "params": {},
          "image": {
            "samples": [
              [
                80,
                80,
                [
                  255,
                  0,
                  0
                ]
              ]
            ]
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "canvas.getContext('2d').fillStyle='blue';canvas.getContext('2d').fillRect(0,0,300,300);await new Promise(r=>requestAnimationFrame(r))"
          }
        },
        {
          "method": "screenshot",
          "params": {},
          "image": {
            "samples": [
              [
                80,
                80,
                [
                  0,
                  0,
                  255
                ]
              ]
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--visible"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/screenshot-canvas-update.html"
    },
    {
      "id": "screenshot-clip",
      "title": "A clip contains the intended element pixels",
      "category": "Screenshots",
      "html": "<div id=\"box\"></div>",
      "css": "#box{width:100px;height:80px;background:red}",
      "setup": "",
      "description": "A clip contains the intended element pixels",
      "steps": [
        {
          "method": "rect",
          "params": {
            "selector": "#box"
          },
          "capture": {
            "clip": "result"
          }
        },
        {
          "method": "screenshot",
          "params": {
            "clip": "{{clip}}"
          },
          "image": {
            "cssWidth": 100,
            "cssHeight": 80,
            "samples": [
              [
                30,
                30,
                [
                  255,
                  0,
                  0
                ]
              ]
            ]
          }
        }
      ],
      "contract": "required",
      "requires": [
        "clip"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/screenshot-clip.html"
    },
    {
      "id": "screenshot-clip-windows",
      "title": "Windows explicitly rejects screenshot clipping",
      "category": "Screenshots",
      "html": "<p>Clip support</p>",
      "css": "",
      "setup": "",
      "description": "Windows explicitly rejects screenshot clipping",
      "steps": [
        {
          "method": "screenshot",
          "params": {
            "clip": {
              "x": 0,
              "y": 0,
              "width": 100,
              "height": 100
            }
          },
          "error": "not supported"
        }
      ],
      "contract": "required",
      "requires": [
        "windows"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/screenshot-clip-windows.html"
    },
    {
      "id": "screenshot-full-restore",
      "title": "Full-page capture includes the lower page and restores viewport",
      "category": "Screenshots",
      "html": "<div id=\"tall\"></div>",
      "css": "#tall{height:1400px;background:linear-gradient(red,blue)}",
      "setup": "",
      "description": "Full-page capture includes the lower page and restores viewport",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "[innerWidth,innerHeight]"
          },
          "capture": {
            "size": "result"
          }
        },
        {
          "method": "screenshot",
          "params": {
            "full": true
          },
          "image": {
            "minHeight": 1200
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>innerHeight===640);return [innerWidth,innerHeight]"
          },
          "expect": [
            960,
            640
          ]
        }
      ],
      "contract": "required",
      "requires": [
        "linux"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/screenshot-full-restore.html"
    },
    {
      "id": "image-decoded",
      "title": "Image readiness includes successful decoding",
      "category": "Rendering",
      "html": "<img id=\"image\" src=\"../assets/pattern.svg\" alt=\"Fixture pattern\">",
      "css": "",
      "setup": "",
      "description": "Image readiness includes successful decoding",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await image.decode();return image.naturalWidth"
          },
          "expect": 160
        },
        {
          "method": "screenshot",
          "params": {},
          "image": {
            "minWidth": 100
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/image-decoded.html"
    },
    {
      "id": "image-broken",
      "title": "Broken image has zero natural width and useful alt text",
      "category": "Rendering",
      "html": "<img id=\"image\" src=\"../assets/intentionally-missing.png\" alt=\"Unavailable illustration\">",
      "css": "",
      "setup": "",
      "description": "Broken image has zero natural width and useful alt text",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "({value: (image.complete && image.naturalWidth===0)})"
          },
          "expect": {
            "value": true
          }
        },
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "verify": "result.elements.some(e=>e.name==='Unavailable illustration')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/image-broken.html"
    },
    {
      "id": "render-mathml",
      "title": "MathML remains visible and surrounding controls work",
      "category": "Rendering",
      "html": "<math><mfrac><mn>1</mn><mn>2</mn></mfrac></math><button id=\"target\" onclick=\"__lab.state.clicked=true\">Continue</button>",
      "css": "",
      "setup": "",
      "description": "MathML remains visible and surrounding controls work",
      "steps": [
        {
          "method": "screenshot",
          "params": {},
          "image": {
            "minWidth": 100
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#target"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.clicked)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/render-mathml.html"
    },
    {
      "id": "render-media",
      "title": "Media elements expose stable poster and playback state",
      "category": "Rendering",
      "html": "<video id=\"video\" controls poster=\"../assets/pattern.svg\" width=\"320\"></video><audio id=\"audio\" controls></audio>",
      "css": "",
      "setup": "",
      "description": "Media elements expose stable poster and playback state",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "({value: (video.paused && audio.paused)})"
          },
          "expect": {
            "value": true
          }
        },
        {
          "method": "screenshot",
          "params": {},
          "image": {
            "minWidth": 320
          }
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "typeof HTMLMediaElement==='function' && video instanceof HTMLMediaElement && audio instanceof HTMLMediaElement",
          "reason": "Native media elements unavailable in this engine/runtime"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/render-media.html"
    },
    {
      "id": "color-scheme",
      "title": "Dark and light set the documented page color scheme",
      "category": "Rendering",
      "html": "<input id=\"field\">",
      "css": "",
      "setup": "",
      "description": "Dark and light set the documented page color scheme",
      "steps": [
        {
          "method": "dark",
          "params": {}
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (getComputedStyle(document.documentElement).colorScheme)})"
          },
          "expect": {
            "value": "dark"
          }
        },
        {
          "method": "light",
          "params": {}
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (getComputedStyle(document.documentElement).colorScheme)})"
          },
          "expect": {
            "value": "light"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/color-scheme.html"
    },
    {
      "id": "resize-invalid-0-640",
      "title": "Reject viewport 0×640",
      "category": "Boundaries",
      "html": "<p>Viewport</p>",
      "css": "",
      "setup": "",
      "description": "Reject viewport 0×640",
      "steps": [
        {
          "method": "resize",
          "params": {
            "width": 0,
            "height": 640
          },
          "error": "between"
        },
        {
          "method": "resize",
          "params": {
            "width": 960,
            "height": 640
          },
          "expect": {
            "width": 960,
            "height": 640
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/resize-invalid-0-640.html"
    },
    {
      "id": "resize-invalid-960-0",
      "title": "Reject viewport 960×0",
      "category": "Boundaries",
      "html": "<p>Viewport</p>",
      "css": "",
      "setup": "",
      "description": "Reject viewport 960×0",
      "steps": [
        {
          "method": "resize",
          "params": {
            "width": 960,
            "height": 0
          },
          "error": "between"
        },
        {
          "method": "resize",
          "params": {
            "width": 960,
            "height": 640
          },
          "expect": {
            "width": 960,
            "height": 640
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/resize-invalid-960-0.html"
    },
    {
      "id": "resize-invalid-16001-1",
      "title": "Reject viewport 16001×1",
      "category": "Boundaries",
      "html": "<p>Viewport</p>",
      "css": "",
      "setup": "",
      "description": "Reject viewport 16001×1",
      "steps": [
        {
          "method": "resize",
          "params": {
            "width": 16001,
            "height": 1
          },
          "error": "between"
        },
        {
          "method": "resize",
          "params": {
            "width": 960,
            "height": 640
          },
          "expect": {
            "width": 960,
            "height": 640
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/resize-invalid-16001-1.html"
    },
    {
      "id": "framework-react",
      "title": "react controlled input survives a rerender",
      "category": "Frameworks",
      "html": "<div id=\"app\"></div>",
      "css": "",
      "setup": "await __lab.until(()=>document.getElementById('field'))",
      "description": "react controlled input survives a rerender",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "héllo controlled"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#rerender"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>document.getElementById('model').textContent==='héllo controlled')"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.getElementById('field').value)})"
          },
          "expect": {
            "value": "héllo controlled"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#submit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.submitted)})"
          },
          "expect": {
            "value": "héllo controlled"
          }
        }
      ],
      "contract": "required",
      "requires": [
        "framework:react"
      ],
      "tier": "core",
      "scripts": [
        "frameworks/react.js"
      ],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/framework-react.html"
    },
    {
      "id": "framework-vue",
      "title": "vue controlled input survives a rerender",
      "category": "Frameworks",
      "html": "<div id=\"app\"></div>",
      "css": "",
      "setup": "await __lab.until(()=>document.getElementById('field'))",
      "description": "vue controlled input survives a rerender",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "héllo controlled"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#rerender"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>document.getElementById('model').textContent==='héllo controlled')"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.getElementById('field').value)})"
          },
          "expect": {
            "value": "héllo controlled"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#submit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.submitted)})"
          },
          "expect": {
            "value": "héllo controlled"
          }
        }
      ],
      "contract": "required",
      "requires": [
        "framework:vue"
      ],
      "tier": "core",
      "scripts": [
        "frameworks/vue.js"
      ],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/framework-vue.html"
    },
    {
      "id": "framework-svelte",
      "title": "svelte controlled input survives a rerender",
      "category": "Frameworks",
      "html": "<div id=\"app\"></div>",
      "css": "",
      "setup": "await __lab.until(()=>document.getElementById('field'))",
      "description": "svelte controlled input survives a rerender",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "héllo controlled"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#rerender"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>document.getElementById('model').textContent==='héllo controlled')"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.getElementById('field').value)})"
          },
          "expect": {
            "value": "héllo controlled"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#submit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.submitted)})"
          },
          "expect": {
            "value": "héllo controlled"
          }
        }
      ],
      "contract": "required",
      "requires": [
        "framework:svelte"
      ],
      "tier": "core",
      "scripts": [
        "frameworks/svelte.js"
      ],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/framework-svelte.html"
    },
    {
      "id": "workflow-search",
      "title": "Search selects the correct item despite duplicate names",
      "category": "Workflows",
      "html": "<input id=\"query\" aria-label=\"Search\"><button id=\"search\">Search</button><div id=\"results\"></div>",
      "css": "",
      "setup": "search.onclick=()=>{results.innerHTML='<button id=item-1>Blue backpack $70</button><button id=item-2>Blue backpack $50</button>';results.onclick=e=>{if(e.target.id.startsWith('item-'))__lab.state.selected=Number(e.target.id.slice(5))}}",
      "description": "Search selects the correct item despite duplicate names",
      "steps": [
        {
          "method": "fill",
          "params": {
            "selector": "#query",
            "value": "blue"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#search"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "selector": "#item-2"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#item-2"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.selected)})"
          },
          "expect": {
            "value": 2
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/workflow-search.html"
    },
    {
      "id": "workflow-decoy-login",
      "title": "Visible login fields receive values while hidden decoys stay untouched",
      "category": "Workflows",
      "html": "<input name=\"email\" hidden value=\"decoy\"><form id=\"login\"><label>Email<input id=\"email\" name=\"email\"></label><label>Password<input id=\"password\" name=\"password\" type=\"password\"></label><button id=\"submit\">Sign in</button></form>",
      "css": "",
      "setup": "__lab.state.submits=0;login.onsubmit=e=>{e.preventDefault();__lab.state.submits++;__lab.state.values=Object.fromEntries(new FormData(login))}",
      "description": "Visible login fields receive values while hidden decoys stay untouched",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "capture": {
            "emailRef": "result.elements.find(e=>e.role==='textbox'&&e.name==='Email').ref"
          }
        },
        {
          "method": "fill",
          "params": {
            "ref": "{{emailRef}}",
            "value": "qa@example.test"
          }
        },
        {
          "method": "fill",
          "params": {
            "selector": "#password",
            "value": "fixture-only"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#submit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.values)})"
          },
          "expect": {
            "value": {
              "email": "qa@example.test",
              "password": "fixture-only"
            }
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.querySelector('input[hidden]').value)})"
          },
          "expect": {
            "value": "decoy"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.submits)})"
          },
          "expect": {
            "value": 1
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/workflow-decoy-login.html"
    },
    {
      "id": "workflow-modal-cancel",
      "title": "Canceling a modal edit preserves the record",
      "category": "Workflows",
      "html": "<button id=\"edit\">Edit record</button><dialog id=\"modal\"><input id=\"field\"><button id=\"cancel\">Cancel</button><button id=\"save\">Save</button></dialog>",
      "css": "",
      "setup": "__lab.state.record='original';edit.onclick=()=>{field.value=__lab.state.record;modal.showModal()};cancel.onclick=()=>modal.close();save.onclick=()=>{__lab.state.record=field.value;modal.close()}",
      "description": "Canceling a modal edit preserves the record",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#edit"
          }
        },
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "changed"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#cancel"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.record)})"
          },
          "expect": {
            "value": "original"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#edit"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (field.value)})"
          },
          "expect": {
            "value": "original"
          }
        },
        {
          "method": "fill",
          "params": {
            "selector": "#field",
            "value": "saved"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#save"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.record)})"
          },
          "expect": {
            "value": "saved"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/workflow-modal-cancel.html"
    },
    {
      "id": "workflow-virtualized",
      "title": "Recycled DOM nodes are rediscovered after virtual scrolling",
      "category": "Workflows",
      "html": "<button id=\"next\">Next records</button><div id=\"rows\"></div>",
      "css": "",
      "setup": "const render=n=>{rows.innerHTML=Array.from({length:5},(_,i)=>'<button data-record='+ (n+i) +'>Record '+(n+i)+'</button>').join('')};render(0);next.onclick=()=>render(10);rows.onclick=e=>{if(e.target.dataset.record)__lab.state.selected=Number(e.target.dataset.record)}",
      "description": "Recycled DOM nodes are rediscovered after virtual scrolling",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#next"
          }
        },
        {
          "method": "find",
          "params": {
            "text": "Record 12",
            "format": "json"
          },
          "capture": {
            "ref": "result.elements[0].ref"
          }
        },
        {
          "method": "click",
          "params": {
            "ref": "{{ref}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.selected)})"
          },
          "expect": {
            "value": 12
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/workflow-virtualized.html"
    },
    {
      "id": "wait-insert-attached",
      "title": "Wait for an explicitly released attached element",
      "category": "Waiting",
      "html": "<div id=\"mount\"></div>",
      "css": "",
      "setup": "",
      "description": "Wait for an explicitly released attached element",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "selector": "#late",
            "state": "attached",
            "timeout": 4000
          },
          "send": "wait"
        },
        {
          "method": "eval",
          "params": {
            "expression": "mount.innerHTML='<button id=late>Ready</button>'"
          }
        },
        {
          "await": "wait"
        },
        {
          "method": "click",
          "params": {
            "selector": "#late"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.activeElement.id)})"
          },
          "expect": {
            "value": "late"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-insert-attached.html"
    },
    {
      "id": "wait-insert-visible",
      "title": "Wait for an explicitly released visible element",
      "category": "Waiting",
      "html": "<div id=\"mount\"></div>",
      "css": "",
      "setup": "",
      "description": "Wait for an explicitly released visible element",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "selector": "#late",
            "state": "visible",
            "timeout": 4000
          },
          "send": "wait"
        },
        {
          "method": "eval",
          "params": {
            "expression": "mount.innerHTML='<button id=late>Ready</button>'"
          }
        },
        {
          "await": "wait"
        },
        {
          "method": "click",
          "params": {
            "selector": "#late"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.activeElement.id)})"
          },
          "expect": {
            "value": "late"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-insert-visible.html"
    },
    {
      "id": "wait-hidden-attached",
      "title": "Attached and visible are distinct states",
      "category": "Waiting",
      "html": "<button id=\"target\" hidden>Target</button>",
      "css": "",
      "setup": "",
      "description": "Attached and visible are distinct states",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "selector": "#target",
            "state": "attached",
            "timeout": 200
          }
        },
        {
          "method": "wait_for",
          "params": {
            "selector": "#target",
            "state": "visible",
            "timeout": 4000
          },
          "send": "wait"
        },
        {
          "method": "eval",
          "params": {
            "expression": "target.hidden=false"
          }
        },
        {
          "await": "wait"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (target.hidden)})"
          },
          "expect": {
            "value": false
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-hidden-attached.html"
    },
    {
      "id": "wait-disappear",
      "title": "Hidden wait resolves after element removal",
      "category": "Waiting",
      "html": "<button id=\"target\">Target</button>",
      "css": "",
      "setup": "",
      "description": "Hidden wait resolves after element removal",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "selector": "#target",
            "state": "hidden",
            "timeout": 4000
          },
          "send": "wait"
        },
        {
          "method": "eval",
          "params": {
            "expression": "target.remove()"
          }
        },
        {
          "await": "wait"
        },
        {
          "method": "count",
          "params": {
            "selector": "#target"
          },
          "expect": 0
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-disappear.html"
    },
    {
      "id": "wait-detached-ref",
      "title": "A detached reference must not satisfy attached",
      "category": "Waiting",
      "html": "<button id=\"target\">Target</button>",
      "css": "",
      "setup": "",
      "description": "A detached reference must not satisfy attached",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "format": "json"
          },
          "capture": {
            "ref": "result.elements[0].ref"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "target.remove()"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "ref": "{{ref}}",
            "state": "attached",
            "timeout": 150
          },
          "error": ""
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-detached-ref.html"
    },
    {
      "id": "wait-text",
      "title": "Text present and text gone follow explicit page transitions",
      "category": "Waiting",
      "html": "<p id=\"out\"></p>",
      "css": "",
      "setup": "",
      "description": "Text present and text gone follow explicit page transitions",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "text": "Ready",
            "timeout": 4000
          },
          "send": "wait"
        },
        {
          "method": "eval",
          "params": {
            "expression": "out.textContent='Ready'"
          }
        },
        {
          "await": "wait"
        },
        {
          "method": "wait_for",
          "params": {
            "textGone": "Ready",
            "timeout": 4000
          },
          "send": "gone"
        },
        {
          "method": "eval",
          "params": {
            "expression": "out.textContent=''"
          }
        },
        {
          "await": "gone"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (out.textContent)})"
          },
          "expect": {
            "value": ""
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-text.html"
    },
    {
      "id": "wait-timeout",
      "title": "A missing condition times out and leaves the session usable",
      "category": "Waiting",
      "html": "<p id=\"out\">Healthy</p>",
      "css": "",
      "setup": "",
      "description": "A missing condition times out and leaves the session usable",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "selector": "#missing",
            "timeout": 150
          },
          "error": "timed out"
        },
        {
          "method": "text",
          "params": {
            "selector": "#out"
          },
          "expect": "Healthy"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-timeout.html"
    },
    {
      "id": "wait-zero-time",
      "title": "Explicit zero-duration wait completes immediately",
      "category": "Waiting",
      "html": "<p>Zero time</p>",
      "css": "",
      "setup": "",
      "description": "Explicit zero-duration wait completes immediately",
      "steps": [
        {
          "method": "wait_for",
          "params": {
            "time": 0
          },
          "expect": null
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/wait-zero-time.html"
    },
    {
      "id": "async-promise",
      "title": "Wait for a promise DOM update",
      "category": "Async JavaScript",
      "html": "<button id=\"start\">Start</button><div id=\"marker\"></div><p id=\"out\"></p>",
      "css": "",
      "setup": "const change=()=>out.textContent='Finished';start.onclick=()=>{Promise.resolve().then(change)}",
      "description": "Wait for a promise DOM update",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Finished",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (out.textContent)})"
          },
          "expect": {
            "value": "Finished"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/async-promise.html"
    },
    {
      "id": "async-microtask",
      "title": "Wait for a microtask DOM update",
      "category": "Async JavaScript",
      "html": "<button id=\"start\">Start</button><div id=\"marker\"></div><p id=\"out\"></p>",
      "css": "",
      "setup": "const change=()=>out.textContent='Finished';start.onclick=()=>{queueMicrotask(change)}",
      "description": "Wait for a microtask DOM update",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Finished",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (out.textContent)})"
          },
          "expect": {
            "value": "Finished"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/async-microtask.html"
    },
    {
      "id": "async-timer",
      "title": "Wait for a timer DOM update",
      "category": "Async JavaScript",
      "html": "<button id=\"start\">Start</button><div id=\"marker\"></div><p id=\"out\"></p>",
      "css": "",
      "setup": "const change=()=>out.textContent='Finished';start.onclick=()=>{setTimeout(change,20)}",
      "description": "Wait for a timer DOM update",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Finished",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (out.textContent)})"
          },
          "expect": {
            "value": "Finished"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/async-timer.html"
    },
    {
      "id": "async-animation",
      "title": "Wait for a animation DOM update",
      "category": "Async JavaScript",
      "html": "<button id=\"start\">Start</button><div id=\"marker\"></div><p id=\"out\"></p>",
      "css": "",
      "setup": "const change=()=>out.textContent='Finished';start.onclick=()=>{requestAnimationFrame(change)}",
      "description": "Wait for a animation DOM update",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Finished",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (out.textContent)})"
          },
          "expect": {
            "value": "Finished"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--visible"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/async-animation.html"
    },
    {
      "id": "async-observer",
      "title": "Wait for a observer DOM update",
      "category": "Async JavaScript",
      "html": "<button id=\"start\">Start</button><div id=\"marker\"></div><p id=\"out\"></p>",
      "css": "",
      "setup": "const change=()=>out.textContent='Finished';start.onclick=()=>{new MutationObserver(change).observe(marker,{attributes:true});marker.dataset.trigger='yes'}",
      "description": "Wait for a observer DOM update",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Finished",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (out.textContent)})"
          },
          "expect": {
            "value": "Finished"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/async-observer.html"
    },
    {
      "id": "request-late-reply",
      "title": "Expired running promise produces only one reply",
      "category": "Timeouts",
      "html": "<p>Promise fixture</p>",
      "css": "",
      "setup": "",
      "description": "Expired running promise produces only one reply",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "new Promise(resolve=>window.release=resolve)",
            "request_timeout": 150
          },
          "error": "timed out"
        },
        {
          "method": "eval",
          "params": {
            "expression": "release(42)"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (21*2)})"
          },
          "expect": {
            "value": 42
          }
        },
        {
          "assertNoDuplicateReplies": true
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/request-late-reply.html"
    },
    {
      "id": "request-zero",
      "title": "A zero-deadline operation never executes",
      "category": "Timeouts",
      "html": "<p>Zero deadline</p>",
      "css": "",
      "setup": "",
      "description": "A zero-deadline operation never executes",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "window.unexpected=true",
            "request_timeout": 0
          },
          "error": "timed out"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.unexpected===undefined)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/request-zero.html"
    },
    {
      "id": "request-queued-expiry",
      "title": "Operation expiring behind navigation never runs afterward",
      "category": "Timeouts",
      "html": "<a id=\"link\" href=\"../__lab/hang\">Slow destination</a>",
      "css": "",
      "setup": "",
      "description": "Operation expiring behind navigation never runs afterward",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#link"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "window.expired=true",
            "request_timeout": 150
          },
          "error": "timed out"
        },
        {
          "method": "navigate",
          "params": {
            "url": "{{site}}support/other.html",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.expired===undefined)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/request-queued-expiry.html"
    },
    {
      "id": "navigation-hash",
      "title": "Hash navigation keeps the document and updates URL",
      "category": "Navigation",
      "html": "<a id=\"link\" href=\"#section\">Section</a><p id=\"section\">Section content</p>",
      "css": "",
      "setup": "",
      "description": "Hash navigation keeps the document and updates URL",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "window.marker=42"
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#link"
          }
        },
        {
          "method": "url",
          "params": {},
          "verify": "result.endsWith('#section')"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.marker)})"
          },
          "expect": {
            "value": 42
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-hash.html"
    },
    {
      "id": "navigation-history-state",
      "title": "Same-URL history restores the correct state object",
      "category": "Navigation",
      "html": "<p>History</p>",
      "css": "",
      "setup": "",
      "description": "Same-URL history restores the correct state object",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "history.pushState({step:1},'',location.href);history.pushState({step:2},'',location.href)"
          }
        },
        {
          "method": "back",
          "params": {}
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (history.state.step)})"
          },
          "expect": {
            "value": 1
          }
        },
        {
          "method": "forward",
          "params": {}
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (history.state.step)})"
          },
          "expect": {
            "value": 2
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-history-state.html"
    },
    {
      "id": "navigation-pushstate",
      "title": "PushState and back expose the correct URL and document",
      "category": "Navigation",
      "html": "<button id=\"go\">Next</button>",
      "css": "",
      "setup": "go.onclick=()=>history.pushState({},'', '?route=next')",
      "description": "PushState and back expose the correct URL and document",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#go"
          }
        },
        {
          "method": "url",
          "params": {},
          "verify": "result.includes('route=next')"
        },
        {
          "method": "back",
          "params": {}
        },
        {
          "method": "url",
          "params": {},
          "verify": "!result.includes('route=next')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-pushstate.html"
    },
    {
      "id": "navigation-same-url",
      "title": "Repeated same-URL navigation loads a fresh document",
      "category": "Navigation",
      "html": "<p>Reload me</p>",
      "css": "",
      "setup": "",
      "description": "Repeated same-URL navigation loads a fresh document",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "window.marker=42"
          }
        },
        {
          "method": "navigate",
          "params": {
            "url": "{{caseUrl}}",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.marker===undefined)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-same-url.html"
    },
    {
      "id": "navigation-cancelled",
      "title": "Canceled link navigation does not stall later calls",
      "category": "Navigation",
      "html": "<a id=\"link\" href=\"../support/other.html\">Stay</a>",
      "css": "",
      "setup": "link.onclick=e=>{e.preventDefault();__lab.state.cancelled=true}",
      "description": "Canceled link navigation does not stall later calls",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#link"
          }
        },
        {
          "method": "url",
          "params": {},
          "verify": "result.includes('navigation-cancelled')"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.cancelled)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-cancelled.html"
    },
    {
      "id": "navigation-reload",
      "title": "Reload discards document-local mutations",
      "category": "Navigation",
      "html": "<p>Reload</p>",
      "css": "",
      "setup": "",
      "description": "Reload discards document-local mutations",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "window.marker=42"
          }
        },
        {
          "method": "reload",
          "params": {}
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.marker===undefined)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-reload.html"
    },
    {
      "id": "navigation-redirect",
      "title": "HTTP redirects return the final URL",
      "category": "Navigation",
      "html": "<p>Redirect</p>",
      "css": "",
      "setup": "",
      "description": "HTTP redirects return the final URL",
      "steps": [
        {
          "method": "navigate",
          "params": {
            "url": "{{site}}__lab/redirect?count=3",
            "timeout": 5000
          }
        },
        {
          "method": "url",
          "params": {},
          "verify": "result.endsWith('/support/other.html')"
        },
        {
          "method": "title",
          "params": {},
          "expect": "Other fixture"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-redirect.html"
    },
    {
      "id": "navigation-dom",
      "title": "DOM navigation completes before a controlled slow image",
      "category": "Navigation",
      "html": "<p>Load milestones</p>",
      "css": "",
      "setup": "",
      "description": "DOM navigation completes before a controlled slow image",
      "steps": [
        {
          "method": "navigate",
          "params": {
            "url": "{{site}}__lab/slow-page",
            "until": "dom",
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.imageFinished)})"
          },
          "expect": {
            "value": false
          }
        },
        {
          "server": "release",
          "gate": "image"
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Image finished",
            "timeout": 5000
          }
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-dom.html"
    },
    {
      "id": "navigation-load",
      "title": "Load navigation waits for a gated subresource",
      "category": "Navigation",
      "html": "<p>Load milestones</p>",
      "css": "",
      "setup": "",
      "description": "Load navigation waits for a gated subresource",
      "steps": [
        {
          "method": "navigate",
          "params": {
            "url": "{{site}}__lab/slow-page",
            "until": "load",
            "timeout": 5000
          },
          "send": "nav"
        },
        {
          "server": "wait",
          "gate": "image"
        },
        {
          "server": "release",
          "gate": "image"
        },
        {
          "await": "nav"
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (window.imageFinished)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-load.html"
    },
    {
      "id": "navigation-failure-recovery",
      "title": "A connection failure is followed by successful navigation",
      "category": "Navigation",
      "html": "<p>Failure recovery</p>",
      "css": "",
      "setup": "",
      "description": "A connection failure is followed by successful navigation",
      "steps": [
        {
          "method": "navigate",
          "params": {
            "url": "{{closedOrigin}}/",
            "timeout": 1000
          },
          "error": ""
        },
        {
          "method": "navigate",
          "params": {
            "url": "{{site}}support/other.html",
            "timeout": 5000
          }
        },
        {
          "method": "title",
          "params": {},
          "expect": "Other fixture"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/navigation-failure-recovery.html"
    },
    {
      "id": "tab-new-current",
      "title": "New tab is ready without changing the current tab",
      "category": "Tabs",
      "html": "<p>Original tab</p>",
      "css": "",
      "setup": "",
      "description": "New tab is ready without changing the current tab",
      "steps": [
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "title",
          "params": {},
          "verify": "result.includes('tab-new-current')"
        },
        {
          "method": "title",
          "params": {
            "tab": "{{tab}}"
          },
          "expect": "Other fixture"
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/tab-new-current.html"
    },
    {
      "id": "tab-switch",
      "title": "Switching tabs changes the default command target",
      "category": "Tabs",
      "html": "<p>Original tab</p>",
      "css": "",
      "setup": "",
      "description": "Switching tabs changes the default command target",
      "steps": [
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "switch_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "title",
          "params": {},
          "expect": "Other fixture"
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "title",
          "params": {},
          "verify": "result.includes('tab-switch')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/tab-switch.html"
    },
    {
      "id": "tab-close-pending",
      "title": "Closing a tab answers its pending work",
      "category": "Tabs",
      "html": "<p>Original tab</p>",
      "css": "",
      "setup": "",
      "description": "Closing a tab answers its pending work",
      "steps": [
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "tab": "{{tab}}",
            "selector": "#never",
            "timeout": 5000
          },
          "send": "wait"
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "await": "wait",
          "error": "tab closed"
        },
        {
          "method": "title",
          "params": {},
          "verify": "result.includes('tab-close-pending')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/tab-close-pending.html"
    },
    {
      "id": "tab-last",
      "title": "Closing the last tab fails without closing the session",
      "category": "Tabs",
      "html": "<p>Last tab</p>",
      "css": "",
      "setup": "",
      "description": "Closing the last tab fails without closing the session",
      "steps": [
        {
          "method": "close_tab",
          "params": {},
          "error": "last tab"
        },
        {
          "method": "tabs",
          "params": {},
          "verify": "result.length===1"
        },
        {
          "method": "title",
          "params": {},
          "verify": "result.includes('tab-last')"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/tab-last.html"
    },
    {
      "id": "tab-blank-link",
      "title": "A blank-target link creates exactly one tab",
      "category": "Tabs",
      "html": "<a id=\"link\" href=\"../support/other.html\" target=\"_blank\">Open</a>",
      "css": "",
      "setup": "",
      "description": "A blank-target link creates exactly one tab",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#link"
          }
        },
        {
          "method": "tabs",
          "params": {},
          "verify": "result.length===2",
          "capture": {
            "tab": "result.find(t=>!t.current).tab"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "tab": "{{tab}}",
            "text": "Other page",
            "timeout": 5000
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/tab-blank-link.html"
    },
    {
      "id": "tab-first-init",
      "title": "Init script executes before the first page script in a new tab",
      "category": "Tabs",
      "html": "<p>Init</p>",
      "css": "",
      "setup": "",
      "description": "Init script executes before the first page script in a new tab",
      "steps": [
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/init.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "title",
          "params": {
            "tab": "{{tab}}"
          },
          "expect": "seeded"
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--init-script",
        "{{initScript}}"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/tab-first-init.html"
    },
    {
      "id": "launch-user-agent",
      "title": "Configured user agent reaches initial and subsequent tabs",
      "category": "Launch options",
      "html": "<p>User agent</p>",
      "css": "",
      "setup": "",
      "description": "Configured user agent reaches initial and subsequent tabs",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "({value: (navigator.userAgent)})"
          },
          "expect": {
            "value": "bruster-test-lab"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "eval",
          "params": {
            "tab": "{{tab}}",
            "expression": "navigator.userAgent"
          },
          "expect": "bruster-test-lab"
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--user-agent",
        "bruster-test-lab"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/launch-user-agent.html"
    },
    {
      "id": "fetch-string",
      "title": "Fetch accepts a string input",
      "category": "Network",
      "html": "<p>Fetch fixture</p>",
      "css": "",
      "setup": "",
      "description": "Fetch accepts a string input",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/echo')).json()"
          },
          "verify": "result.method==='GET'"
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch' && e.status===200)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-string.html"
    },
    {
      "id": "fetch-request",
      "title": "Fetch accepts a request input",
      "category": "Network",
      "html": "<p>Fetch fixture</p>",
      "css": "",
      "setup": "",
      "description": "Fetch accepts a request input",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch(new Request('../__lab/echo'))).json()"
          },
          "verify": "result.method==='GET'"
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch' && e.status===200)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-request.html"
    },
    {
      "id": "fetch-url",
      "title": "Fetch accepts a url input",
      "category": "Network",
      "html": "<p>Fetch fixture</p>",
      "css": "",
      "setup": "",
      "description": "Fetch accepts a url input",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch(new URL('../__lab/echo',location.href))).json()"
          },
          "verify": "result.method==='GET'"
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch' && e.status===200)"
        }
      ],
      "contract": "probe",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-url.html"
    },
    {
      "id": "fetch-status-200",
      "title": "HTTP 200 remains a response rather than a transport error",
      "category": "Network",
      "html": "<p>HTTP status</p>",
      "css": "",
      "setup": "",
      "description": "HTTP 200 remains a response rather than a transport error",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/status?code=200')).status"
          },
          "expect": 200
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch'&&e.status===200)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-status-200.html"
    },
    {
      "id": "fetch-status-204",
      "title": "HTTP 204 remains a response rather than a transport error",
      "category": "Network",
      "html": "<p>HTTP status</p>",
      "css": "",
      "setup": "",
      "description": "HTTP 204 remains a response rather than a transport error",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/status?code=204')).status"
          },
          "expect": 204
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch'&&e.status===204)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-status-204.html"
    },
    {
      "id": "fetch-status-404",
      "title": "HTTP 404 remains a response rather than a transport error",
      "category": "Network",
      "html": "<p>HTTP status</p>",
      "css": "",
      "setup": "",
      "description": "HTTP 404 remains a response rather than a transport error",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/status?code=404')).status"
          },
          "expect": 404
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch'&&e.status===404)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-status-404.html"
    },
    {
      "id": "fetch-status-500",
      "title": "HTTP 500 remains a response rather than a transport error",
      "category": "Network",
      "html": "<p>HTTP status</p>",
      "css": "",
      "setup": "",
      "description": "HTTP 500 remains a response rather than a transport error",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/status?code=500')).status"
          },
          "expect": 500
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='fetch'&&e.status===500)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-status-500.html"
    },
    {
      "id": "fetch-headers",
      "title": "Request headers reach the server and response headers remain inspectable",
      "category": "Network",
      "html": "<p>Headers</p>",
      "css": "",
      "setup": "",
      "description": "Request headers reach the server and response headers remain inspectable",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/echo',{headers:{'X-Lab':'fixture'}})).json()"
          },
          "verify": "result.headers['x-lab']==='fixture'"
        },
        {
          "method": "network",
          "params": {},
          "capture": {
            "index": "result.find(e=>e.type==='fetch').index"
          }
        },
        {
          "method": "network_request",
          "params": {
            "index": "{{index}}"
          },
          "verify": "result.req_headers['x-lab']==='fixture' && result.res_headers['x-lab-response']==='fixture'"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-headers.html"
    },
    {
      "id": "fetch-post",
      "title": "POST body and method reach the HTTP fixture exactly",
      "category": "Network",
      "html": "<p>POST</p>",
      "css": "",
      "setup": "",
      "description": "POST body and method reach the HTTP fixture exactly",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/echo',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:'héllo'})})).json()"
          },
          "verify": "result.method==='POST' && JSON.parse(result.body).text==='héllo'"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-post.html"
    },
    {
      "id": "xhr-text",
      "title": "XHR text response is preserved by instrumentation",
      "category": "Network",
      "html": "<p>XHR</p>",
      "css": "",
      "setup": "",
      "description": "XHR text response is preserved by instrumentation",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise((resolve,reject)=>{const x=new XMLHttpRequest();x.open('GET','../__lab/echo');x.responseType='text';x.onload=()=>resolve(x.status);x.onerror=reject;x.send()})"
          },
          "expect": 200
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='xhr'&&e.status===200)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/xhr-text.html"
    },
    {
      "id": "xhr-json",
      "title": "XHR json response is preserved by instrumentation",
      "category": "Network",
      "html": "<p>XHR</p>",
      "css": "",
      "setup": "",
      "description": "XHR json response is preserved by instrumentation",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise((resolve,reject)=>{const x=new XMLHttpRequest();x.open('GET','../__lab/echo');x.responseType='json';x.onload=()=>resolve(x.status);x.onerror=reject;x.send()})"
          },
          "expect": 200
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='xhr'&&e.status===200)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/xhr-json.html"
    },
    {
      "id": "xhr-arraybuffer",
      "title": "XHR arraybuffer response is preserved by instrumentation",
      "category": "Network",
      "html": "<p>XHR</p>",
      "css": "",
      "setup": "",
      "description": "XHR arraybuffer response is preserved by instrumentation",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise((resolve,reject)=>{const x=new XMLHttpRequest();x.open('GET','../__lab/echo');x.responseType='arraybuffer';x.onload=()=>resolve(x.status);x.onerror=reject;x.send()})"
          },
          "expect": 200
        },
        {
          "method": "network",
          "params": {},
          "verify": "result.some(e=>e.type==='xhr'&&e.status===200)"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/xhr-arraybuffer.html"
    },
    {
      "id": "fetch-abort",
      "title": "Aborted fetch records failure and permits later requests",
      "category": "Network",
      "html": "<p>Abort</p>",
      "css": "",
      "setup": "",
      "description": "Aborted fetch records failure and permits later requests",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise(resolve=>{const c=new AbortController();fetch('../__lab/hang',{signal:c.signal}).then(()=>resolve('unexpected'),e=>resolve(e.name));c.abort()})"
          },
          "expect": "AbortError"
        },
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/echo')).status"
          },
          "expect": 200
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/fetch-abort.html"
    },
    {
      "id": "network-body-16383",
      "title": "ASCII response body prefix at 16383 characters",
      "category": "Network",
      "html": "<p>Body prefix</p>",
      "css": "",
      "setup": "",
      "description": "ASCII response body prefix at 16383 characters",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await (await fetch('../__lab/body?size=16383')).text()"
          },
          "verify": "result.length===16383"
        },
        {
          "method": "network",
          "params": {},
          "capture": {
            "index": "result.find(e=>e.type==='fetch').index"
          }
        },
        {
          "method": "network_request",
          "params": {
            "index": "{{index}}"
          },
          "verify": "typeof result.body==='string' && result.body.length===16383"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-body-16383.html"
    },
    {
      "id": "network-body-16384",
      "title": "ASCII response body prefix at 16384 characters",
      "category": "Network",
      "html": "<p>Body prefix</p>",
      "css": "",
      "setup": "",
      "description": "ASCII response body prefix at 16384 characters",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await (await fetch('../__lab/body?size=16384')).text()"
          },
          "verify": "result.length===16384"
        },
        {
          "method": "network",
          "params": {},
          "capture": {
            "index": "result.find(e=>e.type==='fetch').index"
          }
        },
        {
          "method": "network_request",
          "params": {
            "index": "{{index}}"
          },
          "verify": "typeof result.body==='string' && result.body.length===16384"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-body-16384.html"
    },
    {
      "id": "network-body-16385",
      "title": "ASCII response body prefix at 16385 characters",
      "category": "Network",
      "html": "<p>Body prefix</p>",
      "css": "",
      "setup": "",
      "description": "ASCII response body prefix at 16385 characters",
      "steps": [
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await (await fetch('../__lab/body?size=16385')).text()"
          },
          "verify": "result.length===16385"
        },
        {
          "method": "network",
          "params": {},
          "capture": {
            "index": "result.find(e=>e.type==='fetch').index"
          }
        },
        {
          "method": "network_request",
          "params": {
            "index": "{{index}}"
          },
          "verify": "typeof result.body==='string' && result.body.length===16384"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-body-16385.html"
    },
    {
      "id": "network-clear",
      "title": "Clearing request logs resets entries and indexes",
      "category": "Network",
      "html": "<p>Clear logs</p>",
      "css": "",
      "setup": "",
      "description": "Clearing request logs resets entries and indexes",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await (await fetch('../__lab/echo')).text()"
          }
        },
        {
          "method": "network",
          "params": {
            "clear": true
          }
        },
        {
          "method": "network",
          "params": {},
          "expect": []
        },
        {
          "method": "network_request",
          "params": {
            "index": 0
          },
          "error": "no fetch/xhr entry"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-clear.html"
    },
    {
      "id": "network-idle-gate",
      "title": "Network idle cannot complete while a gated request is pending",
      "category": "Network",
      "html": "<button id=\"start\">Start request</button>",
      "css": "",
      "setup": "start.onclick=()=>fetch('../__lab/gate?name=fetch').then(r=>r.text()).then(()=>__lab.state.finished=true)",
      "description": "Network idle cannot complete while a gated request is pending",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "server": "wait",
          "gate": "fetch"
        },
        {
          "method": "wait_for",
          "params": {
            "idle": true,
            "timeout": 150
          },
          "error": "timed out"
        },
        {
          "server": "release",
          "gate": "fetch"
        },
        {
          "method": "wait_for",
          "params": {
            "idle": true,
            "timeout": 5000
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.finished)})"
          },
          "expect": {
            "value": true
          }
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-idle-gate.html"
    },
    {
      "id": "network-cors",
      "title": "A second origin supports explicitly permitted CORS",
      "category": "Network",
      "html": "<p>CORS</p>",
      "css": "",
      "setup": "",
      "description": "A second origin supports explicitly permitted CORS",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch(__lab.server.otherOrigin+__lab.sitePath+'__lab/echo?cors=1')).json()"
          },
          "verify": "result.method==='GET'"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-cors.html"
    },
    {
      "id": "network-cors-denied",
      "title": "A denied cross-origin request fails without breaking same-origin requests",
      "category": "Network",
      "html": "<p>CORS denied</p>",
      "css": "",
      "setup": "",
      "description": "A denied cross-origin request fails without breaking same-origin requests",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await fetch(__lab.server.otherOrigin+__lab.sitePath+'__lab/echo')"
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "(await fetch('../__lab/echo')).status"
          },
          "expect": 200
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/network-cors-denied.html"
    },
    {
      "id": "cookie-set-read",
      "title": "Native cookie setting is visible in the page and other tabs",
      "category": "Storage",
      "html": "<p>Cookies</p>",
      "css": "",
      "setup": "",
      "description": "Native cookie setting is visible in the page and other tabs",
      "steps": [
        {
          "method": "set_cookie",
          "params": {
            "name": "lab_cookie",
            "value": "fixture",
            "path": "{{sitePath}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (document.cookie.includes('lab_cookie=fixture'))})"
          },
          "expect": {
            "value": true
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "eval",
          "params": {
            "tab": "{{tab}}",
            "expression": "document.cookie.includes('lab_cookie=fixture')"
          },
          "expect": true
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/cookie-set-read.html"
    },
    {
      "id": "cookie-server",
      "title": "Server-set cookies reach the native cookie API",
      "category": "Storage",
      "html": "<p>Server cookies</p>",
      "css": "",
      "setup": "",
      "description": "Server-set cookies reach the native cookie API",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await fetch('../__lab/cookie')"
          }
        },
        {
          "method": "cookies",
          "params": {},
          "verify": "result.some(c=>c.name==='lab_server'&&c.value==='fixture')"
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/cookie-server.html"
    },
    {
      "id": "cookie-paths",
      "title": "Same-named cookies on distinct paths remain distinguishable",
      "category": "Storage",
      "html": "<p>Cookie paths</p>",
      "css": "",
      "setup": "",
      "description": "Same-named cookies on distinct paths remain distinguishable",
      "steps": [
        {
          "method": "set_cookie",
          "params": {
            "name": "lab_path",
            "value": "root",
            "path": "/"
          }
        },
        {
          "method": "set_cookie",
          "params": {
            "name": "lab_path",
            "value": "site",
            "path": "{{sitePath}}"
          }
        },
        {
          "method": "cookies",
          "params": {},
          "verify": "result.filter(c=>c.name==='lab_path').length===2"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/cookie-paths.html"
    },
    {
      "id": "storage-tabs",
      "title": "Local storage is shared while new-tab session storage is independent",
      "category": "Storage",
      "html": "<p>Tab storage</p>",
      "css": "",
      "setup": "",
      "description": "Local storage is shared while new-tab session storage is independent",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "localStorage.setItem('lab_local','shared');sessionStorage.setItem('lab_session','private')"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "eval",
          "params": {
            "tab": "{{tab}}",
            "expression": "[localStorage.getItem('lab_local'),sessionStorage.getItem('lab_session')]"
          },
          "expect": [
            "shared",
            null
          ]
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/storage-tabs.html"
    },
    {
      "id": "storage-port-origin",
      "title": "Different ports isolate local storage",
      "category": "Storage",
      "html": "<p>Port origins</p>",
      "css": "",
      "setup": "",
      "description": "Different ports isolate local storage",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "localStorage.setItem('lab_origin','private')"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{otherSite}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "eval",
          "params": {
            "tab": "{{tab}}",
            "expression": "localStorage.getItem('lab_origin')"
          },
          "expect": null
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        }
      ],
      "contract": "required",
      "requires": [
        "server"
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/storage-port-origin.html"
    },
    {
      "id": "profile-persistence",
      "title": "Named profile preserves local storage across process restart",
      "category": "Profiles",
      "html": "<p>Persistent profile</p>",
      "css": "",
      "setup": "",
      "description": "Named profile preserves local storage across process restart",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "localStorage.setItem('lab_persist','kept')"
          }
        },
        {
          "restart": true
        },
        {
          "method": "navigate",
          "params": {
            "url": "{{caseUrl}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (localStorage.getItem('lab_persist'))})"
          },
          "expect": {
            "value": "kept"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [
        "--profile",
        "{{profile}}"
      ],
      "viewport": [
        960,
        640
      ],
      "url": "cases/profile-persistence.html"
    },
    {
      "id": "session-isolation",
      "title": "Profile-free process restart discards local storage",
      "category": "Profiles",
      "html": "<p>Session isolation</p>",
      "css": "",
      "setup": "",
      "description": "Profile-free process restart discards local storage",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "localStorage.setItem('lab_persist','discarded')"
          }
        },
        {
          "restart": true
        },
        {
          "method": "navigate",
          "params": {
            "url": "{{caseUrl}}"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (localStorage.getItem('lab_persist'))})"
          },
          "expect": {
            "value": null
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/session-isolation.html"
    },
    {
      "id": "indexeddb",
      "title": "IndexedDB writes are readable after their transaction commits",
      "category": "Storage",
      "html": "<p>IndexedDB</p>",
      "css": "",
      "setup": "",
      "description": "IndexedDB writes are readable after their transaction commits",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise((resolve,reject)=>{const r=indexedDB.open('lab-fixture',1);r.onupgradeneeded=()=>r.result.createObjectStore('items');r.onerror=()=>reject(r.error);r.onsuccess=()=>{const db=r.result;const tx=db.transaction('items','readwrite');tx.objectStore('items').put({value:'stored'},'key');tx.oncomplete=()=>{db.close();resolve(true)}}})"
          },
          "expect": true
        },
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise((resolve,reject)=>{const r=indexedDB.open('lab-fixture',1);r.onsuccess=()=>{const db=r.result;const read=db.transaction('items').objectStore('items').get('key');read.onsuccess=()=>{db.close();resolve(read.result)}};r.onerror=()=>reject(r.error)})"
          },
          "expect": {
            "value": "stored"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/indexeddb.html"
    },
    {
      "id": "worker-message",
      "title": "Dedicated worker communication survives page instrumentation",
      "category": "Workers",
      "html": "<p>Worker</p>",
      "css": "",
      "setup": "",
      "description": "Dedicated worker communication survives page instrumentation",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "await new Promise((resolve,reject)=>{const w=new Worker('../support/worker.js');w.onmessage=e=>{w.terminate();resolve(e.data)};w.onerror=reject;w.postMessage(21)})"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/worker-message.html"
    },
    {
      "id": "service-worker",
      "title": "Service worker registration is scoped and explicitly removable",
      "category": "Workers",
      "html": "<p>Service worker</p>",
      "css": "",
      "setup": "",
      "description": "Service worker registration is scoped and explicitly removable",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "const r=await navigator.serviceWorker.register('../support/sw.js');await __lab.until(()=>r.active);return r.scope.endsWith('/support/')"
          },
          "expect": true
        },
        {
          "method": "eval",
          "params": {
            "expression": "const registrations=await navigator.serviceWorker.getRegistrations();const ours=registrations.filter(r=>r.scope.endsWith('/test-lab/support/'));return (await Promise.all(ours.map(r=>r.unregister()))).every(Boolean)"
          },
          "expect": true
        }
      ],
      "contract": "required",
      "requires": [
        {
          "expression": "'serviceWorker' in navigator",
          "reason": "Service workers unavailable in this engine/context"
        }
      ],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/service-worker.html"
    },
    {
      "id": "eval-number",
      "title": "JavaScript return serialization: number",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: number",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "42"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-number.html"
    },
    {
      "id": "eval-string",
      "title": "JavaScript return serialization: string",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: string",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "'héllo 🎉'"
          },
          "expect": "héllo 🎉"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-string.html"
    },
    {
      "id": "eval-boolean",
      "title": "JavaScript return serialization: boolean",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: boolean",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "false"
          },
          "expect": false
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-boolean.html"
    },
    {
      "id": "eval-null",
      "title": "JavaScript return serialization: null",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: null",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "null"
          },
          "expect": null
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-null.html"
    },
    {
      "id": "eval-undefined",
      "title": "JavaScript return serialization: undefined",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: undefined",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "undefined"
          },
          "expect": null
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-undefined.html"
    },
    {
      "id": "eval-array",
      "title": "JavaScript return serialization: array",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: array",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "[1,true,null]"
          },
          "expect": [
            1,
            true,
            null
          ]
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-array.html"
    },
    {
      "id": "eval-object",
      "title": "JavaScript return serialization: object",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: object",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "({nested:{value:3}})"
          },
          "expect": {
            "nested": {
              "value": 3
            }
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-object.html"
    },
    {
      "id": "eval-promise",
      "title": "JavaScript return serialization: promise",
      "category": "Evaluation",
      "html": "<p>Evaluation</p>",
      "css": "",
      "setup": "",
      "description": "JavaScript return serialization: promise",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "Promise.resolve('resolved')"
          },
          "expect": "resolved"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-promise.html"
    },
    {
      "id": "eval-throw",
      "title": "Recover after JavaScript throw",
      "category": "Evaluation",
      "html": "<p>Recovery</p>",
      "css": "",
      "setup": "",
      "description": "Recover after JavaScript throw",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "throw new Error('lab failure')"
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "21*2"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-throw.html"
    },
    {
      "id": "eval-syntax",
      "title": "Recover after JavaScript syntax",
      "category": "Evaluation",
      "html": "<p>Recovery</p>",
      "css": "",
      "setup": "",
      "description": "Recover after JavaScript syntax",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "const = ;"
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "21*2"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-syntax.html"
    },
    {
      "id": "eval-rejection",
      "title": "Recover after JavaScript rejection",
      "category": "Evaluation",
      "html": "<p>Recovery</p>",
      "css": "",
      "setup": "",
      "description": "Recover after JavaScript rejection",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "Promise.reject(new Error('lab failure'))"
          },
          "error": ""
        },
        {
          "method": "eval",
          "params": {
            "expression": "21*2"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-rejection.html"
    },
    {
      "id": "eval-cyclic",
      "title": "Cyclic result uses a JSON-safe fallback",
      "category": "Evaluation",
      "html": "<p>Cycle</p>",
      "css": "",
      "setup": "",
      "description": "Cyclic result uses a JSON-safe fallback",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "const a={};a.self=a;return a"
          },
          "expect": "[object Object]"
        },
        {
          "method": "eval",
          "params": {
            "expression": "42"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-cyclic.html"
    },
    {
      "id": "eval-page-monkeypatch",
      "title": "Replacing page JSON.stringify cannot corrupt replies",
      "category": "Evaluation",
      "html": "<p id=\"out\">Healthy</p>",
      "css": "",
      "setup": "",
      "description": "Replacing page JSON.stringify cannot corrupt replies",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "JSON.stringify=()=> 'broken';document.querySelector=()=>null"
          }
        },
        {
          "method": "text",
          "params": {
            "selector": "#out"
          },
          "expect": "Healthy"
        },
        {
          "method": "eval",
          "params": {
            "expression": "42"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/eval-page-monkeypatch.html"
    },
    {
      "id": "console-log",
      "title": "Console captures level log",
      "category": "Console",
      "html": "<p>Console</p>",
      "css": "",
      "setup": "",
      "description": "Console captures level log",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.log('lab-message',{value:3})"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='log'&&e.text.includes('lab-message')&&e.text.includes('value'))"
        },
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "console",
          "params": {},
          "expect": []
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-log.html"
    },
    {
      "id": "console-info",
      "title": "Console captures level info",
      "category": "Console",
      "html": "<p>Console</p>",
      "css": "",
      "setup": "",
      "description": "Console captures level info",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.info('lab-message',{value:3})"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='info'&&e.text.includes('lab-message')&&e.text.includes('value'))"
        },
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "console",
          "params": {},
          "expect": []
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-info.html"
    },
    {
      "id": "console-warn",
      "title": "Console captures level warn",
      "category": "Console",
      "html": "<p>Console</p>",
      "css": "",
      "setup": "",
      "description": "Console captures level warn",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.warn('lab-message',{value:3})"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='warn'&&e.text.includes('lab-message')&&e.text.includes('value'))"
        },
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "console",
          "params": {},
          "expect": []
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-warn.html"
    },
    {
      "id": "console-error",
      "title": "Console captures level error",
      "category": "Console",
      "html": "<p>Console</p>",
      "css": "",
      "setup": "",
      "description": "Console captures level error",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.error('lab-message',{value:3})"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='error'&&e.text.includes('lab-message')&&e.text.includes('value'))"
        },
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "console",
          "params": {},
          "expect": []
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-error.html"
    },
    {
      "id": "console-debug",
      "title": "Console captures level debug",
      "category": "Console",
      "html": "<p>Console</p>",
      "css": "",
      "setup": "",
      "description": "Console captures level debug",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.debug('lab-message',{value:3})"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='debug'&&e.text.includes('lab-message')&&e.text.includes('value'))"
        },
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "console",
          "params": {},
          "expect": []
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-debug.html"
    },
    {
      "id": "console-length-999",
      "title": "Console entry truncation at 999 characters",
      "category": "Boundaries",
      "html": "<p>Console limits</p>",
      "css": "",
      "setup": "",
      "description": "Console entry truncation at 999 characters",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.log('x'.repeat(999))"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.length===1 && result[0].text.length===999"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-length-999.html"
    },
    {
      "id": "console-length-1000",
      "title": "Console entry truncation at 1000 characters",
      "category": "Boundaries",
      "html": "<p>Console limits</p>",
      "css": "",
      "setup": "",
      "description": "Console entry truncation at 1000 characters",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.log('x'.repeat(1000))"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.length===1 && result[0].text.length===1000"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-length-1000.html"
    },
    {
      "id": "console-length-1001",
      "title": "Console entry truncation at 1001 characters",
      "category": "Boundaries",
      "html": "<p>Console limits</p>",
      "css": "",
      "setup": "",
      "description": "Console entry truncation at 1001 characters",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "console.log('x'.repeat(1001))"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.length===1 && result[0].text.length===1000"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-length-1001.html"
    },
    {
      "id": "console-unhandled",
      "title": "Unhandled promise rejection reaches console diagnostics",
      "category": "Console",
      "html": "<button id=\"start\">Reject</button>",
      "css": "",
      "setup": "window.addEventListener('unhandledrejection',()=>__lab.state.rejected=true);start.onclick=()=>{Promise.reject(new Error('fixture rejection'))}",
      "description": "Unhandled promise rejection reaches console diagnostics",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "await __lab.until(()=>__lab.state.rejected===true)"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='error'&&e.text.includes('fixture rejection'))"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-unhandled.html"
    },
    {
      "id": "console-error-stack",
      "title": "Error diagnostics retain the message when the stack only contains frames",
      "category": "Console",
      "html": "<p>Error formatting</p>",
      "css": "",
      "setup": "",
      "description": "Error diagnostics retain the message when the stack only contains frames",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "const error=new TypeError('fixture failure');error.stack='fixture@source.js:1:2';console.error(error)"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.some(e=>e.level==='error'&&e.text.includes('TypeError: fixture failure')&&e.text.includes('fixture@source.js:1:2'))"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/console-error-stack.html"
    },
    {
      "id": "dialog-confirm-true",
      "title": "Confirm returns true",
      "category": "Dialogs",
      "html": "<button id=\"start\">Confirm</button>",
      "css": "",
      "setup": "start.onclick=()=>__lab.state.answer=confirm('Continue?')",
      "description": "Confirm returns true",
      "steps": [
        {
          "method": "dialog",
          "params": {
            "accept": true,
            "clear": true
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.answer)})"
          },
          "expect": {
            "value": true
          }
        },
        {
          "method": "dialog",
          "params": {},
          "verify": "result.length===1 && result[0].type==='confirm'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/dialog-confirm-true.html"
    },
    {
      "id": "dialog-confirm-false",
      "title": "Confirm returns false",
      "category": "Dialogs",
      "html": "<button id=\"start\">Confirm</button>",
      "css": "",
      "setup": "start.onclick=()=>__lab.state.answer=confirm('Continue?')",
      "description": "Confirm returns false",
      "steps": [
        {
          "method": "dialog",
          "params": {
            "accept": false,
            "clear": true
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.answer)})"
          },
          "expect": {
            "value": false
          }
        },
        {
          "method": "dialog",
          "params": {},
          "verify": "result.length===1 && result[0].type==='confirm'"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/dialog-confirm-false.html"
    },
    {
      "id": "dialog-prompt-text",
      "title": "Prompt response: text",
      "category": "Dialogs",
      "html": "<button id=\"start\">Prompt</button>",
      "css": "",
      "setup": "start.onclick=()=>__lab.state.answer=prompt('Name','default')",
      "description": "Prompt response: text",
      "steps": [
        {
          "method": "dialog",
          "params": {
            "accept": true,
            "text": "custom",
            "clear": true
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.answer)})"
          },
          "expect": {
            "value": "custom"
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/dialog-prompt-text.html"
    },
    {
      "id": "dialog-prompt-cancel",
      "title": "Prompt response: cancel",
      "category": "Dialogs",
      "html": "<button id=\"start\">Prompt</button>",
      "css": "",
      "setup": "start.onclick=()=>__lab.state.answer=prompt('Name','default')",
      "description": "Prompt response: cancel",
      "steps": [
        {
          "method": "dialog",
          "params": {
            "accept": false,
            "clear": true
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.answer)})"
          },
          "expect": {
            "value": null
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/dialog-prompt-cancel.html"
    },
    {
      "id": "dialog-prompt-empty",
      "title": "Prompt response: empty",
      "category": "Dialogs",
      "html": "<button id=\"start\">Prompt</button>",
      "css": "",
      "setup": "start.onclick=()=>__lab.state.answer=prompt('Name','default')",
      "description": "Prompt response: empty",
      "steps": [
        {
          "method": "dialog",
          "params": {
            "accept": true,
            "text": "",
            "clear": true
          }
        },
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.answer)})"
          },
          "expect": {
            "value": ""
          }
        }
      ],
      "contract": "probe",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/dialog-prompt-empty.html"
    },
    {
      "id": "dialog-alert",
      "title": "Alert emits a record and subsequent code runs",
      "category": "Dialogs",
      "html": "<button id=\"start\">Alert</button>",
      "css": "",
      "setup": "start.onclick=()=>{alert('Hello');__lab.state.after=true}",
      "description": "Alert emits a record and subsequent code runs",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#start"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.after)})"
          },
          "expect": {
            "value": true
          }
        },
        {
          "method": "dialog",
          "params": {
            "clear": true
          },
          "verify": "result.length===1&&result[0].type==='alert'"
        },
        {
          "method": "dialog",
          "params": {},
          "expect": []
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/dialog-alert.html"
    },
    {
      "id": "beforeunload",
      "title": "Beforeunload cannot block navigation",
      "category": "Dialogs",
      "html": "<a id=\"leave\" href=\"../support/other.html\">Leave</a>",
      "css": "",
      "setup": "window.addEventListener('beforeunload',e=>{e.preventDefault();e.returnValue='Stay?'})",
      "description": "Beforeunload cannot block navigation",
      "steps": [
        {
          "method": "click",
          "params": {
            "selector": "#leave"
          }
        },
        {
          "method": "wait_for",
          "params": {
            "text": "Other page",
            "timeout": 5000
          }
        },
        {
          "method": "title",
          "params": {},
          "expect": "Other fixture"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "core",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/beforeunload.html"
    },
    {
      "id": "stress-dom-5000",
      "title": "Extract and interact with 5000 controls",
      "category": "Stress",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<5000;i++){const b=document.createElement('button');b.id='item-'+i;b.textContent='Record '+i;b.onclick=()=>__lab.state.selected=i;document.body.append(b)}",
      "description": "Extract and interact with 5000 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "max": 5001,
            "format": "json"
          },
          "verify": "result.elements.length===5000 && !result.truncated"
        },
        {
          "method": "click",
          "params": {
            "selector": "#item-4999"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.selected)})"
          },
          "expect": {
            "value": 4999
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "stress",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/stress-dom-5000.html"
    },
    {
      "id": "stress-dom-10000",
      "title": "Extract and interact with 10000 controls",
      "category": "Stress",
      "html": "",
      "css": "",
      "setup": "for(let i=0;i<10000;i++){const b=document.createElement('button');b.id='item-'+i;b.textContent='Record '+i;b.onclick=()=>__lab.state.selected=i;document.body.append(b)}",
      "description": "Extract and interact with 10000 controls",
      "steps": [
        {
          "method": "snapshot",
          "params": {
            "max": 10001,
            "format": "json"
          },
          "verify": "result.elements.length===10000 && !result.truncated"
        },
        {
          "method": "click",
          "params": {
            "selector": "#item-9999"
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "({value: (__lab.state.selected)})"
          },
          "expect": {
            "value": 9999
          }
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "stress",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/stress-dom-10000.html"
    },
    {
      "id": "stress-console-count",
      "title": "Console collection stays bounded after 501 messages",
      "category": "Stress",
      "html": "<p>Console</p>",
      "css": "",
      "setup": "",
      "description": "Console collection stays bounded after 501 messages",
      "steps": [
        {
          "method": "console",
          "params": {
            "clear": true
          }
        },
        {
          "method": "eval",
          "params": {
            "expression": "for(let i=0;i<501;i++)console.log(i)"
          }
        },
        {
          "method": "console",
          "params": {},
          "verify": "result.length===500"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "stress",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/stress-console-count.html"
    },
    {
      "id": "stress-large-result",
      "title": "A large Unicode result survives protocol framing",
      "category": "Stress",
      "html": "<p>Large reply</p>",
      "css": "",
      "setup": "",
      "description": "A large Unicode result survives protocol framing",
      "steps": [
        {
          "method": "eval",
          "params": {
            "expression": "'日本語🎉'.repeat(20000)"
          },
          "verify": "result.length===100000"
        },
        {
          "method": "eval",
          "params": {
            "expression": "42"
          },
          "expect": 42
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "stress",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/stress-large-result.html"
    },
    {
      "id": "stress-tab-cycles",
      "title": "Repeated tab creation and closure returns to one tab",
      "category": "Stress",
      "html": "<p>Tab cycles</p>",
      "css": "",
      "setup": "",
      "description": "Repeated tab creation and closure returns to one tab",
      "steps": [
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "new_tab",
          "params": {
            "url": "{{site}}support/other.html"
          },
          "capture": {
            "tab": "result.tab"
          }
        },
        {
          "method": "close_tab",
          "params": {
            "tab": "{{tab}}"
          }
        },
        {
          "method": "tabs",
          "params": {},
          "verify": "result.length===1"
        }
      ],
      "contract": "required",
      "requires": [],
      "tier": "stress",
      "scripts": [],
      "launch": [],
      "viewport": [
        960,
        640
      ],
      "url": "cases/stress-tab-cycles.html"
    }
  ]
}
