Files
COMERCIAL-GTI/maquina-comercial-pessoal/tests/browser/campaign-pilot.spec.mjs
T
GustavoandClaude Sonnet 5 417da56bd6 chore: import maquina-comercial-pessoal history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

git-subtree-dir: maquina-comercial-pessoal
git-subtree-mainline: f5af038e19
git-subtree-split: a9ef4d776d
2026-09-28 15:50:17 -03:00

389 lines
14 KiB
JavaScript

import { test, expect } from '@playwright/test';
import { startTestServer } from './server.mjs';
let ctx;
test.beforeAll(async () => {
ctx = await startTestServer();
});
test.afterAll(async () => {
if (ctx) await ctx.teardown();
});
async function loginViaApi() {
const login = await ctx.apiRequest('POST', '/api/auth/login', {
body: { username: 'admin', password: ctx.adminPw }
});
expect(login.status).toBe(200);
const setCookie = login.headers.get('set-cookie');
return setCookie ? setCookie.split(';')[0] : '';
}
async function setCookieOnPage(page, cookie) {
const match = cookie ? cookie.match(/maquina_pessoal_session=([^;]+)/) : null;
if (!match) return;
await page.context().addCookies([{
name: 'maquina_pessoal_session',
value: match[1],
domain: '127.0.0.1',
path: '/',
httpOnly: true,
sameSite: 'Strict'
}]);
}
test.describe('Campaign Pilot Browser Tests', () => {
test.describe('Authentication', () => {
test('login with seeded admin credentials', async ({ page }) => {
await page.goto('/login.html');
await expect(page.locator('#form-login')).toBeVisible();
await page.fill('#login-username', 'admin');
await page.fill('#login-password', ctx.adminPw);
await page.click('button[type="submit"]');
await page.waitForURL('**/', { timeout: 10000 });
await expect(page.locator('#usuario-logado')).toContainText('admin');
});
});
test.describe('Campaign Cards', () => {
let cookie;
test.beforeAll(async () => {
cookie = await loginViaApi();
});
test('campaign tab shows cards with correct status/limits', async ({ page }) => {
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const cards = page.locator('.campaign-card');
const count = await cards.count();
expect(count).toBe(3);
const cardText = await cards.first().textContent();
expect(cardText).toContain('G-Delivery');
expect(cardText).toContain('Semana 1');
expect(cardText).toContain('Limite: 10');
});
test('separate manual/automatic limits display', async ({ page }) => {
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const cardText = await page.locator('.campaign-card').first().textContent();
expect(cardText).toMatch(/Manual|manual/);
expect(cardText).toMatch(/Automático|auto/i);
});
});
test.describe('Campaign Queue Operations (via API)', () => {
let cookie;
test.beforeAll(async () => {
cookie = await loginViaApi();
});
test('prepare creates queue items', async () => {
const { data } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const campaignId = data.campaigns[0].id;
const response = await ctx.apiRequest('POST', `/api/campaigns/${campaignId}/prepare`, {
headers: { cookie }, body: {}
});
expect(response.status).toBe(200);
expect(response.data.summary.previewed).toBeGreaterThanOrEqual(0);
});
test('preview rows show server-rendered text', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const campaignId = campaigns.campaigns[0].id;
await ctx.apiRequest('POST', `/api/campaigns/${campaignId}/prepare`, {
headers: { cookie }, body: {}
});
const { data: queue } = await ctx.apiRequest('GET', `/api/campaigns/${campaignId}/queue`, {
headers: { cookie }
});
expect(queue.queue.length).toBeGreaterThanOrEqual(1);
expect(queue.queue[0].rendered_message).toBeTruthy();
expect(queue.queue[0].status).toBe('preview');
});
test('per-row removal works', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const campaignId = campaigns.campaigns[0].id;
await ctx.apiRequest('POST', `/api/campaigns/${campaignId}/prepare`, {
headers: { cookie }, body: {}
});
const { data: before } = await ctx.apiRequest('GET', `/api/campaigns/${campaignId}/queue?status=preview`, {
headers: { cookie }
});
if (before.queue.length === 0) return;
const firstId = before.queue[0].id;
const removeResponse = await ctx.apiRequest('DELETE', `/api/campaigns/${campaignId}/queue/${firstId}`, {
headers: { cookie }
});
expect(removeResponse.status).toBe(200);
const { data: after } = await ctx.apiRequest('GET', `/api/campaigns/${campaignId}/queue?status=preview`, {
headers: { cookie }
});
expect(after.queue.find((q) => q.id === firstId)).toBeUndefined();
});
test('approval flow works', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const campaign = campaigns.campaigns.find((c) => c.status === 'paused');
if (!campaign) return;
let { data: queue } = await ctx.apiRequest('GET', `/api/campaigns/${campaign.id}/queue?status=preview`, {
headers: { cookie }
});
if (queue.queue.length === 0) {
await ctx.apiRequest('POST', `/api/campaigns/${campaign.id}/prepare`, {
headers: { cookie }, body: {}
});
const { data: refetched } = await ctx.apiRequest('GET', `/api/campaigns/${campaign.id}/queue?status=preview`, {
headers: { cookie }
});
if (refetched.queue.length === 0) return;
queue.queue = refetched.queue;
}
const ids = queue.queue.slice(0, 1).map((q) => q.id);
const approveResponse = await ctx.apiRequest('POST', `/api/campaigns/${campaign.id}/approve`, {
headers: { cookie },
body: { queueIds: ids, date: '2026-07-29' }
});
expect(approveResponse.status).toBe(200);
expect(approveResponse.data.scheduled).toBeGreaterThanOrEqual(1);
const { data: updated } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const updatedCampaign = updated.campaigns.find((c) => c.id === campaign.id);
expect(['ready', 'running']).toContain(updatedCampaign.status);
});
test('pause shows reason', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const campaign = campaigns.campaigns.find((c) => c.status === 'ready' || c.status === 'paused');
if (!campaign) return;
const pauseResponse = await ctx.apiRequest('POST', `/api/campaigns/${campaign.id}/pause`, {
headers: { cookie },
body: { reason: 'revisao manual do operador' }
});
expect(pauseResponse.status).toBe(200);
expect(pauseResponse.data.campaign.pause_reason).toBe('revisao manual do operador');
expect(pauseResponse.data.campaign.status).toBe('paused');
});
test('start succeeds on paused campaign with scheduled items', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
const campaign = campaigns.campaigns.find((c) => c.status === 'paused' || c.status === 'ready');
if (!campaign) return;
const startResponse = await ctx.apiRequest('POST', `/api/campaigns/${campaign.id}/start`, {
headers: { cookie }, body: {}
});
expect(startResponse.status).toBe(200);
expect(startResponse.data.campaign.status).toBe('running');
});
});
test.describe('Stage Classification', () => {
let cookie;
test.beforeAll(async () => {
cookie = await loginViaApi();
});
test('stage classification works via API', async () => {
const leadId = Number(ctx.db.prepare(
"SELECT id FROM leads WHERE empresa LIKE '%Restaurante%' LIMIT 1"
).get()?.id);
if (!leadId) return;
const stageResponse = await ctx.apiRequest('PUT', `/api/campaigns/leads/${leadId}/stage`, {
headers: { cookie },
body: { stage: 'interessado_gti_vendas', note: 'demonstracao agendada' }
});
expect(stageResponse.status).toBe(200);
expect(stageResponse.data.state.stage).toBe('interessado_gti_vendas');
});
test('appointment/demo scheduling form works via API', async () => {
const leadId = Number(ctx.db.prepare(
"SELECT id FROM leads WHERE empresa LIKE '%Pizzaria%' LIMIT 1"
).get()?.id);
if (!leadId) return;
const appointmentResponse = await ctx.apiRequest('POST', `/api/campaigns/leads/${leadId}/appointments`, {
headers: { cookie },
body: {
kind: 'demonstracao',
participantName: 'Maria Silva',
companyName: 'Pizzaria Teste',
preferredDay: '2026-08-05',
preferredTime: '15:30'
}
});
expect(appointmentResponse.status).toBe(201);
expect(appointmentResponse.data.appointment.kind).toBe('demonstracao');
});
test('pending replies section accessible via API', async () => {
const { data } = await ctx.apiRequest('GET', '/api/campaigns/replies/pending', {
headers: { cookie }
});
expect(Array.isArray(data.pending)).toBe(true);
});
test('metrics endpoint returns data', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
if (campaigns.campaigns.length === 0) return;
const campaignId = campaigns.campaigns[0].id;
const { data } = await ctx.apiRequest('GET', `/api/campaigns/${campaignId}/metrics`, {
headers: { cookie }
});
expect(data.metrics).toBeDefined();
});
test('metrics supports segmento and variant filters', async () => {
const { data: campaigns } = await ctx.apiRequest('GET', '/api/campaigns', { headers: { cookie } });
if (campaigns.campaigns.length === 0) return;
const campaignId = campaigns.campaigns[0].id;
const { data: filtered } = await ctx.apiRequest(
'GET',
`/api/campaigns/${campaignId}/metrics?segmento=hamburguerias&variant=A`,
{ headers: { cookie } }
);
expect(filtered.metrics).toBeDefined();
});
});
test.describe('UI Rendering', () => {
let cookie;
test.beforeAll(async () => {
cookie = await loginViaApi();
});
test('pending replies section visible on campaigns page', async ({ page }) => {
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const repliesSection = page.locator('#pending-replies');
await expect(repliesSection).toBeVisible();
const content = await repliesSection.textContent();
expect(content.length).toBeGreaterThan(0);
});
test('stage classification section renders', async ({ page }) => {
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const stageSection = page.locator('#stage-classification');
await expect(stageSection).toBeVisible();
});
test('appointment scheduling form renders', async ({ page }) => {
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const appointmentSection = page.locator('#appointment-form');
await expect(appointmentSection).toBeVisible();
});
test('metrics section renders with filters', async ({ page }) => {
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const metricsFilters = page.locator('#metrics-filters');
await expect(metricsFilters).toBeVisible();
await page.waitForTimeout(1000);
const filterSelects = metricsFilters.locator('select');
const selectCount = await filterSelects.count();
expect(selectCount).toBeGreaterThanOrEqual(1);
});
});
test.describe('Safety', () => {
test('fake provider send count remains zero', async () => {
expect(ctx.fakeSends.length).toBe(0);
});
});
test.describe('Mobile Viewport', () => {
let cookie;
test.beforeAll(async () => {
cookie = await loginViaApi();
});
test('mobile viewport renders campaign cards', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const cards = page.locator('.campaign-card');
const count = await cards.count();
expect(count).toBe(3);
for (let i = 0; i < count; i++) {
await expect(cards.nth(i)).toBeVisible();
}
const queueSection = page.locator('#campaign-queue');
await expect(queueSection).toBeVisible();
});
test('mobile viewport shows full-width controls', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await setCookieOnPage(page, cookie);
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.click('button[data-tab="campanhas"]');
await page.waitForTimeout(1500);
const section = page.locator('#tab-campanhas');
const box = await section.boundingBox();
expect(box.width).toBeGreaterThanOrEqual(350);
});
});
});