diff --git a/suites/playwright/suite.yaml b/suites/playwright/suite.yaml index 7a84aad..fe853b1 100644 --- a/suites/playwright/suite.yaml +++ b/suites/playwright/suite.yaml @@ -1,16 +1,20 @@ image: ghcr.io/agynio/e2e/playwright:v1.59.1-noble workdir: /opt/app/data select: | + suite_tags=("svc_console" "svc_gateway" "svc_threads" "svc_identity" "smoke") + if [ -z "${TAGS:-}" ]; then echo "playwright" exit 0 fi for tag in ${TAGS}; do - if [ "$tag" = "svc_console" ] || [ "$tag" = "smoke" ]; then - echo "$tag" - exit 0 - fi + for suite_tag in "${suite_tags[@]}"; do + if [ "$tag" = "$suite_tag" ]; then + echo "$tag" + exit 0 + fi + done done run: | set -euo pipefail diff --git a/suites/playwright/test/e2e/organization-threads-smoke.spec.ts b/suites/playwright/test/e2e/organization-threads-smoke.spec.ts new file mode 100644 index 0000000..39fa9c3 --- /dev/null +++ b/suites/playwright/test/e2e/organization-threads-smoke.spec.ts @@ -0,0 +1,47 @@ +import { test, expect } from './fixtures'; +import { + createOrganization, + createThread, + createUser, + getMe, + sendThreadMessage, + setSelectedOrganization, +} from './console-api'; + +test.describe('organization-threads-smoke', { + tag: ['@svc_console', '@svc_gateway', '@svc_threads', '@svc_identity', '@smoke'], +}, () => { + test('threads list loads with data', async ({ page }) => { + const organizationId = await createOrganization(page, `e2e-org-threads-smoke-${Date.now()}`); + await setSelectedOrganization(page, organizationId); + const me = await getMe(page); + const identityId = me.user?.meta?.id; + if (!identityId) { + throw new Error('GetMe response missing identity id for threads smoke test.'); + } + + const participantId = await createUser(page, { + email: `e2e-thread-smoke-${Date.now()}@agyn.test`, + nickname: 'thread-smoke', + }); + + const threadId = await createThread(page, { organizationId, participantIds: [participantId] }); + await sendThreadMessage(page, { threadId, senderId: identityId, body: 'Smoke thread message.' }); + + const threadsLoaded = page.waitForResponse( + (resp) => resp.url().includes('ListOrganizationThreads'), + { timeout: 20000 }, + ); + + await page.goto(`/organizations/${organizationId}/threads`); + const threadsResponse = await threadsLoaded; + expect(threadsResponse.status()).toBe(200); + await expect(page.getByTestId('organization-threads-table')).toBeVisible({ timeout: 20000 }); + const rows = page.getByTestId('organization-thread-row'); + const matchingRow = rows.filter({ hasText: threadId }); + await expect(matchingRow).toBeVisible({ timeout: 20000 }); + await expect(page.getByTestId('organization-threads-empty')).toHaveCount(0, { timeout: 20000 }); + await expect(page.getByText('Failed to load threads.')).toHaveCount(0); + await expect(page.getByText('You do not have permission to view threads.')).toHaveCount(0); + }); +}); diff --git a/suites/playwright/test/e2e/organization-threads.spec.ts b/suites/playwright/test/e2e/organization-threads.spec.ts index 6f6f3ab..c3cf347 100644 --- a/suites/playwright/test/e2e/organization-threads.spec.ts +++ b/suites/playwright/test/e2e/organization-threads.spec.ts @@ -9,7 +9,7 @@ import { setSelectedOrganization, } from './console-api'; -test.describe('organization-threads', { tag: ['@svc_console'] }, () => { +test.describe('organization-threads', { tag: ['@svc_console', '@svc_gateway', '@svc_threads', '@svc_identity'] }, () => { test('org threads list and detail pagination', async ({ page }) => { const organizationId = await createOrganization(page, `e2e-org-threads-${Date.now()}`); await setSelectedOrganization(page, organizationId); diff --git a/suites/playwright/test/e2e/workloads-layout.spec.ts b/suites/playwright/test/e2e/workloads-layout.spec.ts new file mode 100644 index 0000000..595ee29 --- /dev/null +++ b/suites/playwright/test/e2e/workloads-layout.spec.ts @@ -0,0 +1,29 @@ +import { test, expect } from './fixtures'; +import { createOrganization, setSelectedOrganization } from './console-api'; + +test.describe('workloads-layout', { tag: ['@svc_console', '@svc_gateway', '@smoke'] }, () => { + test('workloads header lays out across columns', async ({ page }) => { + const organizationId = await createOrganization(page, `e2e-workloads-layout-${Date.now()}`); + await setSelectedOrganization(page, organizationId); + + await page.goto(`/organizations/${organizationId}/activity/workloads`); + const header = page.getByTestId('organization-workloads-header'); + await expect(header).toBeVisible({ timeout: 15000 }); + await header.scrollIntoViewIfNeeded(); + + const agentHeader = header.getByRole('button', { name: 'Agent' }); + const statusHeader = header.getByRole('button', { name: 'Status' }); + await expect(agentHeader).toBeVisible(); + await expect(statusHeader).toBeVisible(); + + const agentBox = await agentHeader.boundingBox(); + const statusBox = await statusHeader.boundingBox(); + if (!agentBox || !statusBox) { + throw new Error('Workloads header bounding boxes missing.'); + } + + const rowOffset = Math.abs(agentBox.y - statusBox.y); + expect(rowOffset).toBeLessThan(24); + expect(statusBox.x).toBeGreaterThan(agentBox.x + 120); + }); +});