|
| 1 | +import { chromium } from 'playwright'; |
| 2 | + |
| 3 | +const AZ = '4172a33a-f9ab-4f6b-86f0-85f0d0f2d93d'; |
| 4 | +const AWS = 'a7dffab5-4824-4678-8431-8eb0f3c2751e'; |
| 5 | +const S = process.env.SCRATCH; |
| 6 | + |
| 7 | +const browser = await chromium.launch(); |
| 8 | +const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); |
| 9 | +await ctx.addInitScript(() => localStorage.setItem('servicehub_tour_completed', 'true')); |
| 10 | +const page = await ctx.newPage(); |
| 11 | + |
| 12 | +const errors = []; |
| 13 | +const results = []; |
| 14 | +page.on('response', async r => { |
| 15 | + if (r.url().includes('/api/') && r.status() >= 400 && !r.url().includes('simulator/status')) { |
| 16 | + let b=''; try{b=(await r.text()).slice(0,100);}catch{} |
| 17 | + errors.push(`${r.status()} ${r.request().method()} ${r.url().replace('http://localhost:3000','').split('?')[0]} ${b}`); |
| 18 | + } |
| 19 | +}); |
| 20 | +page.on('pageerror', e => errors.push('PAGEERROR: ' + String(e).slice(0, 120))); |
| 21 | +const prov = () => page.evaluate(() => document.documentElement.dataset.provider); |
| 22 | +const ok = (name, cond) => { results.push(`${cond ? 'PASS' : 'FAIL'} ${name}`); }; |
| 23 | +const shot = (n) => page.screenshot({ path: `${S}/sweep-${n}.png` }); |
| 24 | +const toasts = () => page.locator('[role="status"], [class*="toast"]').allTextContents(); |
| 25 | + |
| 26 | +// ════════ AZURE ════════ |
| 27 | +await page.goto(`http://localhost:3000/messages?namespace=${AZ}&queue=testqueue&queueType=active`, { waitUntil: 'load' }); |
| 28 | +await page.waitForTimeout(6000); |
| 29 | +ok('A1 azure theme on queue view', (await prov()) === 'azure'); |
| 30 | +const azTabs = await page.getByRole('button', { name: /Active \(|Dead-Letter \(/ }).allTextContents(); |
| 31 | +ok('A1 azure tab labels', azTabs.length === 2 && azTabs[0].startsWith('Active')); |
| 32 | +await shot('a1-azure-queue'); |
| 33 | + |
| 34 | +// message detail panel |
| 35 | +const card = page.locator('div.cursor-pointer').filter({ hasText: 'azureCheck' }).first(); |
| 36 | +if (await card.count()) { |
| 37 | + await card.click(); await page.waitForTimeout(1500); |
| 38 | + const detailTabs = await page.getByRole('button', { name: /^(Properties|Body|AI Insights|Headers)$/ }).count(); |
| 39 | + ok('A2 detail panel tabs', detailTabs === 4); |
| 40 | + await shot('a2-azure-detail'); |
| 41 | +} else { ok('A2 detail panel (no active msg card)', false); } |
| 42 | + |
| 43 | +// DLQ tab + replay |
| 44 | +await page.getByRole('button', { name: /Dead-Letter \(/ }).click(); |
| 45 | +await page.waitForTimeout(5000); |
| 46 | +const dlqCard = page.locator('div.cursor-pointer').filter({ hasText: 'azureCheck' }).first(); |
| 47 | +try { |
| 48 | + await dlqCard.click({ timeout: 5000 }); await page.waitForTimeout(1200); |
| 49 | + const replayBtn = page.getByRole('button', { name: /^Replay/ }).first(); |
| 50 | + const enabled = (await replayBtn.count()) > 0 && !(await replayBtn.isDisabled()); |
| 51 | + ok('A3 replay enabled on DLQ msg', enabled); |
| 52 | + if (enabled) { |
| 53 | + await replayBtn.click({ timeout: 5000 }); await page.waitForTimeout(600); |
| 54 | + await page.getByRole('button', { name: /^Confirm$/ }).last().click({ timeout: 5000 }); |
| 55 | + await page.waitForTimeout(4000); |
| 56 | + ok('A3 replay toast', (await toasts()).some(t => t.includes('replayed successfully'))); |
| 57 | + } |
| 58 | + await shot('a3-azure-replay'); |
| 59 | +} catch (e) { ok('A3 replay flow: ' + String(e).slice(0, 60), false); } |
| 60 | + |
| 61 | +// FAB: send message |
| 62 | +await page.locator('button[title="Open message menu"]').click(); await page.waitForTimeout(500); |
| 63 | +await shot('a4-azure-fab'); |
| 64 | +await page.getByRole('button', { name: /Send Message/ }).click(); await page.waitForTimeout(800); |
| 65 | +await page.getByRole('button', { name: /^Send( Message)?$/i }).last().click(); |
| 66 | +await page.waitForTimeout(3500); |
| 67 | +ok('A4 FAB send toast', (await toasts()).some(t => t.includes('sent successfully') || t.includes('Message sent'))); |
| 68 | + |
| 69 | +// FAB: test DLQ |
| 70 | +await page.locator('button[title="Open message menu"]').click(); await page.waitForTimeout(500); |
| 71 | +await page.getByRole('button', { name: /Test DLQ/ }).click(); |
| 72 | +await page.waitForTimeout(5000); |
| 73 | +ok('A5 FAB test-dlq toast', (await toasts()).some(t => t.includes('DLQ') || t.includes('dead-letter'))); |
| 74 | + |
| 75 | +// topic with no subscriptions |
| 76 | +await page.locator('aside').getByText('testtopic', { exact: true }).click(); |
| 77 | +await page.waitForTimeout(1500); |
| 78 | +const noSubs = await page.locator('aside').getByText(/No subscriptions/i).count(); |
| 79 | +ok('A6 topic shows "No subscriptions" hint', noSubs > 0); |
| 80 | +await shot('a6-azure-topic'); |
| 81 | + |
| 82 | +// dashboard blue |
| 83 | +await page.goto('http://localhost:3000/dashboard', { waitUntil: 'load' }); |
| 84 | +await page.waitForTimeout(3000); |
| 85 | +ok('A7 dashboard stays azure theme', (await prov()) === 'azure'); |
| 86 | +await shot('a7-azure-dashboard'); |
| 87 | + |
| 88 | +// quick access pages under azure theme |
| 89 | +for (const [path, n] of [['/rules','a8-rules'],['/dlq-history','a8-dlq-history'],['/health','a8-health'],['/scheduled','a8-scheduled'],['/cross-cloud-trace','a8-trace'],['/audit','a8-audit'],['/cloud-bridge','a8-bridge']]) { |
| 90 | + await page.goto('http://localhost:3000' + path, { waitUntil: 'load' }).catch(()=>{}); |
| 91 | + await page.waitForTimeout(2200); |
| 92 | + await shot(n); |
| 93 | +} |
| 94 | +ok('A8 quick-access pages visited', true); |
| 95 | + |
| 96 | +// ════════ AWS ════════ |
| 97 | +await page.goto(`http://localhost:3000/messages?namespace=${AZ}&queue=testqueue&queueType=active`, { waitUntil: 'load' }); |
| 98 | +await page.waitForTimeout(2500); |
| 99 | +await page.locator('aside').getByText('DevAWS').click(); |
| 100 | +await page.waitForTimeout(800); |
| 101 | +ok('B1 click AWS ns header → orange', (await prov()) === 'aws'); |
| 102 | + |
| 103 | +await page.goto(`http://localhost:3000/messages?namespace=${AWS}&queue=servicehub-study-orders&queueType=active`, { waitUntil: 'load' }); |
| 104 | +await page.waitForTimeout(5000); |
| 105 | +const awsTabs = await page.getByRole('button', { name: /Queue \(|DLQ \(/ }).allTextContents(); |
| 106 | +ok('B1 AWS tab labels Queue/DLQ', awsTabs.length === 2); |
| 107 | +ok('B1 AWS notice banner', (await page.locator('text=AWS SQS counts every view').count()) > 0); |
| 108 | +await shot('b1-aws-queue'); |
| 109 | + |
| 110 | +// nested DLQ + replay |
| 111 | +await page.locator('aside').getByText('DLQ: servicehub-study-orders-dlq').click(); |
| 112 | +await page.waitForTimeout(8000); |
| 113 | +await shot('b2-aws-dlq'); |
| 114 | +const awsDlqCard = page.locator('div.cursor-pointer').filter({ hasText: /STUDY-1|plain text|xml|orderId|note/ }).first(); |
| 115 | +try { |
| 116 | + await awsDlqCard.click({ timeout: 8000 }); await page.waitForTimeout(1200); |
| 117 | + const rb = page.getByRole('button', { name: /^Replay/ }).first(); |
| 118 | + const en = (await rb.count()) && !(await rb.isDisabled()); |
| 119 | + ok('B2 AWS replay enabled', !!en); |
| 120 | + if (en) { |
| 121 | + await rb.click({ timeout: 5000 }); await page.waitForTimeout(600); |
| 122 | + await page.getByRole('button', { name: /^Confirm$/ }).last().click({ timeout: 5000 }); |
| 123 | + await page.waitForTimeout(6000); |
| 124 | + ok('B2 AWS replay toast', (await toasts()).some(t => t.includes('replayed successfully'))); |
| 125 | + } |
| 126 | +} catch (e) { ok('B2 AWS replay flow: ' + String(e).slice(0, 60), false); } |
| 127 | + |
| 128 | +// topic fan-out |
| 129 | +await page.locator('aside').getByText('servicehub-study-ev').first().click(); |
| 130 | +await page.waitForTimeout(3500); |
| 131 | +ok('B3 fan-out dashboard', (await page.locator('[data-testid="aws-topic-fanout"]').count()) > 0); |
| 132 | +await shot('b3-aws-fanout'); |
| 133 | +await page.getByRole('button', { name: /Publish message/ }).click({ timeout: 8000 }).catch(() => ok('B3 publish button', false)); await page.waitForTimeout(800); |
| 134 | +await page.getByRole('button', { name: /^Send( Message)?$/i }).last().click(); |
| 135 | +await page.waitForTimeout(3500); |
| 136 | +ok('B3 fan-out publish toast', (await toasts()).some(t => t.includes('sent') || t.includes('Sent'))); |
| 137 | + |
| 138 | +// FAB on AWS all enabled |
| 139 | +await page.getByRole('button', { name: /View messages/ }).first().click(); await page.waitForTimeout(2500); |
| 140 | +await page.locator('button[title="Open message menu"]').click(); await page.waitForTimeout(500); |
| 141 | +let fabOk = true; |
| 142 | +for (const nm of ['Test DLQ','Generate Messages','Send Message']) { |
| 143 | + const b = page.getByRole('button', { name: new RegExp(nm) }).first(); |
| 144 | + if (!(await b.count()) || (await b.isDisabled())) fabOk = false; |
| 145 | +} |
| 146 | +ok('B4 AWS FAB all actions enabled', fabOk); |
| 147 | +await shot('b4-aws-fab'); |
| 148 | + |
| 149 | +// dashboard orange |
| 150 | +await page.goto('http://localhost:3000/dashboard', { waitUntil: 'load' }); |
| 151 | +await page.waitForTimeout(3000); |
| 152 | +ok('B5 dashboard stays aws theme', (await prov()) === 'aws'); |
| 153 | +await shot('b5-aws-dashboard'); |
| 154 | + |
| 155 | +console.log(results.join('\n')); |
| 156 | +console.log('\nAPI/page errors:', errors.length ? '\n' + [...new Set(errors)].join('\n') : 'none'); |
| 157 | +await browser.close(); |
0 commit comments