{
  "config": {
    "configFile": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/playwright.config.ts",
    "rootDir": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real",
    "forbidOnly": false,
    "fullyParallel": false,
    "globalSetup": null,
    "globalTeardown": null,
    "globalTimeout": 0,
    "grep": {},
    "grepInvert": null,
    "maxFailures": 0,
    "metadata": {
      "actualWorkers": 1
    },
    "preserveOutput": "always",
    "projects": [
      {
        "outputDir": "/Users/rvanvuren/Projects/alqove-web/.cache/scheduling-qa/E2E-0d4345c5b94c18e8/test-results",
        "repeatEach": 1,
        "retries": 0,
        "metadata": {
          "actualWorkers": 1
        },
        "id": "",
        "name": "",
        "testDir": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real",
        "testIgnore": [],
        "testMatch": [
          "/.*\\.spec\\.ts$/"
        ],
        "timeout": 90000
      }
    ],
    "quiet": false,
    "reporter": [
      [
        "list",
        null
      ],
      [
        "json",
        {
          "outputFile": "/Users/rvanvuren/Projects/alqove-web/.cache/scheduling-qa/E2E-0d4345c5b94c18e8/results.json"
        }
      ]
    ],
    "reportSlowTests": {
      "max": 5,
      "threshold": 300000
    },
    "shard": null,
    "tags": [],
    "updateSnapshots": "missing",
    "updateSourceMethod": "patch",
    "version": "1.60.0",
    "workers": 1,
    "webServer": null
  },
  "suites": [
    {
      "title": "60-invitations.spec.ts",
      "file": "60-invitations.spec.ts",
      "column": 0,
      "line": 0,
      "specs": [
        {
          "title": "invitations: existing account continues login, explicitly accepts, discovers and selects staff store",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 90000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "",
              "projectName": "",
              "results": [
                {
                  "workerIndex": 0,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 1480,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatchObject\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[32m- Expected  - 1\u001b[39m\n\u001b[31m+ Received  + 0\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[2m    \"role\": \"sales\",\u001b[22m\n\u001b[32m-   \"status\": \"active\",\u001b[39m\n\u001b[2m    \"store_id\": \"2411257c-52d5-3a6e-932a-e14cbd30c516\",\u001b[22m\n\u001b[2m  }\u001b[22m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatchObject\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[32m- Expected  - 1\u001b[39m\n\u001b[31m+ Received  + 0\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[2m    \"role\": \"sales\",\u001b[22m\n\u001b[32m-   \"status\": \"active\",\u001b[39m\n\u001b[2m    \"store_id\": \"2411257c-52d5-3a6e-932a-e14cbd30c516\",\u001b[22m\n\u001b[2m  }\u001b[22m\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:116:22",
                    "location": {
                      "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                      "column": 22,
                      "line": 116
                    },
                    "snippet": "  114 |   expect(acceptance.status()).toBe(201);\n  115 |   const membership = (await acceptance.json()).data;\n> 116 |   expect(membership).toMatchObject({ store_id: invite.store_id, role: 'sales', status: 'active' });\n      |                      ^\n  117 |   const discovery = await discovered;\n  118 |   expect(discovery.status()).toBe(200);\n  119 |   const memberships = (await discovery.json()).data as { id: string; store_id: string }[];"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                        "column": 22,
                        "line": 116
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatchObject\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[32m- Expected  - 1\u001b[39m\n\u001b[31m+ Received  + 0\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[2m    \"role\": \"sales\",\u001b[22m\n\u001b[32m-   \"status\": \"active\",\u001b[39m\n\u001b[2m    \"store_id\": \"2411257c-52d5-3a6e-932a-e14cbd30c516\",\u001b[22m\n\u001b[2m  }\u001b[22m\n\n  114 |   expect(acceptance.status()).toBe(201);\n  115 |   const membership = (await acceptance.json()).data;\n> 116 |   expect(membership).toMatchObject({ store_id: invite.store_id, role: 'sales', status: 'active' });\n      |                      ^\n  117 |   const discovery = await discovered;\n  118 |   expect(discovery.status()).toBe(200);\n  119 |   const memberships = (await discovery.json()).data as { id: string; store_id: string }[];\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:116:22"
                    },
                    {
                      "location": {
                        "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                        "column": 27,
                        "line": 111
                      },
                      "message": "Error: page.waitForResponse: Test ended.\n\n  109 |   // Register both real response observers before the explicit UI command.\n  110 |   const accepted = page.waitForResponse(isAccept);\n> 111 |   const discovered = page.waitForResponse(isDiscovery);\n      |                           ^\n  112 |   await page.getByRole('button', { name: 'Accept invitation', exact: true }).click();\n  113 |   const acceptance = await accepted;\n  114 |   expect(acceptance.status()).toBe(201);\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:111:27"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-06T19:40:03.391Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/Users/rvanvuren/Projects/alqove-web/.cache/scheduling-qa/E2E-0d4345c5b94c18e8/test-results/60-invitations-invitations-35cab-ers-and-selects-staff-store/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                    "column": 22,
                    "line": 116
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "97c671db4a66e3f18a26-a4ee7c364c5f696c7d37",
          "file": "60-invitations.spec.ts",
          "line": 95,
          "column": 5
        },
        {
          "title": "invitations: wrong account fails closed without membership or acceptance",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 90000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "",
              "projectName": "",
              "results": [
                {
                  "workerIndex": 1,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 775,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('alert')\nExpected substring: \u001b[32m\"Could not accept this invitation.\"\u001b[39m\nError: strict mode violation: getByRole('alert') resolved to 2 elements:\n    1) <p role=\"alert\" class=\"mt-4 text-sm text-red-700\">Could not accept this invitation. It may be expir…</p> aka getByText('Could not accept this')\n    2) <div role=\"alert\" aria-live=\"assertive\" id=\"__next-route-announcer__\">Store invitation | Alqove</div> aka getByText('Store invitation | Alqove')\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 20000ms\u001b[22m\n\u001b[2m  - waiting for getByRole('alert')\u001b[22m\n",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('alert')\nExpected substring: \u001b[32m\"Could not accept this invitation.\"\u001b[39m\nError: strict mode violation: getByRole('alert') resolved to 2 elements:\n    1) <p role=\"alert\" class=\"mt-4 text-sm text-red-700\">Could not accept this invitation. It may be expir…</p> aka getByText('Could not accept this')\n    2) <div role=\"alert\" aria-live=\"assertive\" id=\"__next-route-announcer__\">Store invitation | Alqove</div> aka getByText('Store invitation | Alqove')\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 20000ms\u001b[22m\n\u001b[2m  - waiting for getByRole('alert')\u001b[22m\n\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:151:41",
                    "location": {
                      "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                      "column": 41,
                      "line": 151
                    },
                    "snippet": "  149 |   expect(response.status()).toBe(422);\n  150 |   expect((await response.json()).errors?.token).toContain('This invitation is for a different account.');\n> 151 |   await expect(page.getByRole('alert')).toContainText('Could not accept this invitation.');\n      |                                         ^\n  152 |   await expect(page.getByRole('button', { name: 'Accept invitation', exact: true })).toBeEnabled();\n  153 |   expect(new URL(page.url()).pathname).toBe('/invitations/accept');\n  154 |   const state = readback();"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                        "column": 41,
                        "line": 151
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('alert')\nExpected substring: \u001b[32m\"Could not accept this invitation.\"\u001b[39m\nError: strict mode violation: getByRole('alert') resolved to 2 elements:\n    1) <p role=\"alert\" class=\"mt-4 text-sm text-red-700\">Could not accept this invitation. It may be expir…</p> aka getByText('Could not accept this')\n    2) <div role=\"alert\" aria-live=\"assertive\" id=\"__next-route-announcer__\">Store invitation | Alqove</div> aka getByText('Store invitation | Alqove')\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 20000ms\u001b[22m\n\u001b[2m  - waiting for getByRole('alert')\u001b[22m\n\n\n  149 |   expect(response.status()).toBe(422);\n  150 |   expect((await response.json()).errors?.token).toContain('This invitation is for a different account.');\n> 151 |   await expect(page.getByRole('alert')).toContainText('Could not accept this invitation.');\n      |                                         ^\n  152 |   await expect(page.getByRole('button', { name: 'Accept invitation', exact: true })).toBeEnabled();\n  153 |   expect(new URL(page.url()).pathname).toBe('/invitations/accept');\n  154 |   const state = readback();\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:151:41"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-06T19:40:06.293Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/Users/rvanvuren/Projects/alqove-web/.cache/scheduling-qa/E2E-0d4345c5b94c18e8/test-results/60-invitations-invitations-69b90-ut-membership-or-acceptance/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                    "column": 41,
                    "line": 151
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "97c671db4a66e3f18a26-12e2c5e73715b0ce9e62",
          "file": "60-invitations.spec.ts",
          "line": 141,
          "column": 5
        },
        {
          "title": "invitations: revoked offer fails closed for the invited existing account",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 90000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "",
              "projectName": "",
              "results": [
                {
                  "workerIndex": 2,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 1072,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('alert')\nExpected substring: \u001b[32m\"Could not accept this invitation.\"\u001b[39m\nError: strict mode violation: getByRole('alert') resolved to 2 elements:\n    1) <p role=\"alert\" class=\"mt-4 text-sm text-red-700\">Could not accept this invitation. It may be expir…</p> aka getByText('Could not accept this')\n    2) <div role=\"alert\" aria-live=\"assertive\" id=\"__next-route-announcer__\">Store invitation | Alqove</div> aka getByText('Store invitation | Alqove')\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 20000ms\u001b[22m\n\u001b[2m  - waiting for getByRole('alert')\u001b[22m\n",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('alert')\nExpected substring: \u001b[32m\"Could not accept this invitation.\"\u001b[39m\nError: strict mode violation: getByRole('alert') resolved to 2 elements:\n    1) <p role=\"alert\" class=\"mt-4 text-sm text-red-700\">Could not accept this invitation. It may be expir…</p> aka getByText('Could not accept this')\n    2) <div role=\"alert\" aria-live=\"assertive\" id=\"__next-route-announcer__\">Store invitation | Alqove</div> aka getByText('Store invitation | Alqove')\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 20000ms\u001b[22m\n\u001b[2m  - waiting for getByRole('alert')\u001b[22m\n\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:179:41",
                    "location": {
                      "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                      "column": 41,
                      "line": 179
                    },
                    "snippet": "  177 |   expect(response.status()).toBe(422);\n  178 |   expect((await response.json()).errors?.token).toContain('This invitation is invalid or has expired.');\n> 179 |   await expect(page.getByRole('alert')).toContainText('Could not accept this invitation.');\n      |                                         ^\n  180 |   expect(new URL(page.url()).pathname).toBe('/invitations/accept');\n  181 |   const state = readback();\n  182 |   assertUnaccepted(state, invite, actor);"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                        "column": 41,
                        "line": 179
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('alert')\nExpected substring: \u001b[32m\"Could not accept this invitation.\"\u001b[39m\nError: strict mode violation: getByRole('alert') resolved to 2 elements:\n    1) <p role=\"alert\" class=\"mt-4 text-sm text-red-700\">Could not accept this invitation. It may be expir…</p> aka getByText('Could not accept this')\n    2) <div role=\"alert\" aria-live=\"assertive\" id=\"__next-route-announcer__\">Store invitation | Alqove</div> aka getByText('Store invitation | Alqove')\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 20000ms\u001b[22m\n\u001b[2m  - waiting for getByRole('alert')\u001b[22m\n\n\n  177 |   expect(response.status()).toBe(422);\n  178 |   expect((await response.json()).errors?.token).toContain('This invitation is invalid or has expired.');\n> 179 |   await expect(page.getByRole('alert')).toContainText('Could not accept this invitation.');\n      |                                         ^\n  180 |   expect(new URL(page.url()).pathname).toBe('/invitations/accept');\n  181 |   const state = readback();\n  182 |   assertUnaccepted(state, invite, actor);\n    at /Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts:179:41"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-06T19:40:07.621Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/Users/rvanvuren/Projects/alqove-web/.cache/scheduling-qa/E2E-0d4345c5b94c18e8/test-results/60-invitations-invitations-4ab1b-he-invited-existing-account/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/Users/rvanvuren/Projects/alqove-web/e2e-scheduling-real/60-invitations.spec.ts",
                    "column": 41,
                    "line": 179
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "97c671db4a66e3f18a26-b8a69e9578ecd491549b",
          "file": "60-invitations.spec.ts",
          "line": 164,
          "column": 5
        }
      ]
    }
  ],
  "errors": [],
  "stats": {
    "startTime": "2026-09-06T19:40:03.028Z",
    "duration": 5970.1539999999995,
    "expected": 0,
    "skipped": 0,
    "unexpected": 3,
    "flaky": 0
  }
}