- Broadcasts: list, create, target, clone, pause/resume, metrics, delete - Surveys: list, create with NPS/questions, conditional logic, activate/pause - Integration: navigation, targeting, incentives, export
382 lines
13 KiB
TypeScript
382 lines
13 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
const ADMIN_EMAIL = 'admin@example.com';
|
|
const ADMIN_PASSWORD = 'Admin123!';
|
|
|
|
async function loginAsAdmin(page: any) {
|
|
await page.goto('/login');
|
|
await page.getByLabel('Email').fill(ADMIN_EMAIL);
|
|
await page.getByLabel('Password').fill(ADMIN_PASSWORD);
|
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
|
// Wait for redirect to dashboard
|
|
await page.waitForURL('**/dashboard', { timeout: 10000 });
|
|
}
|
|
|
|
test.describe('Broadcasts Admin', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
});
|
|
|
|
test('navigates to broadcasts page', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
await expect(page.getByText('Broadcasts')).toBeVisible();
|
|
await expect(page.getByText('Create broadcast')).toBeVisible();
|
|
});
|
|
|
|
test('shows broadcast list with filters', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
|
|
// Check filter dropdowns exist
|
|
await expect(page.getByLabel('Status')).toBeVisible();
|
|
await expect(page.getByLabel('Channel')).toBeVisible();
|
|
|
|
// Check table headers
|
|
await expect(page.getByText('Title')).toBeVisible();
|
|
await expect(page.getByText('Status')).toBeVisible();
|
|
await expect(page.getByText('Channel')).toBeVisible();
|
|
await expect(page.getByText('Metrics')).toBeVisible();
|
|
});
|
|
|
|
test('creates new broadcast draft', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
await page.click('text=Create broadcast');
|
|
|
|
// Should navigate to create page
|
|
await expect(page).toHaveURL(/.*broadcasts\/new/);
|
|
|
|
// Fill basic info
|
|
await page.getByLabel('Title').fill('Test Broadcast');
|
|
await page.getByLabel('Body').fill('This is a test broadcast message');
|
|
|
|
// Select channel
|
|
await page.getByLabel('Channel').selectOption('push');
|
|
await page.getByLabel('Priority').selectOption('normal');
|
|
await page.getByLabel('Style').selectOption('banner');
|
|
|
|
// Save as draft
|
|
await page.click('text=Save Draft');
|
|
|
|
// Should show success and redirect
|
|
await expect(page.getByText('Broadcast saved')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('creates broadcast with targeting', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
await page.click('text=Create broadcast');
|
|
|
|
// Basic info
|
|
await page.getByLabel('Title').fill('Targeted Broadcast');
|
|
await page.getByLabel('Body').fill('For pro users only');
|
|
|
|
// Go to targeting tab
|
|
await page.click('text=Targeting');
|
|
|
|
// Select platforms
|
|
await page.getByLabel('iOS').check();
|
|
await page.getByLabel('Android').check();
|
|
|
|
// Select user segments
|
|
await page.getByLabel('User Segments').selectOption(['pro']);
|
|
|
|
// Set percentage rollout
|
|
await page.getByLabel('Percentage Rollout').fill('50');
|
|
|
|
// Go to schedule tab
|
|
await page.click('text=Schedule');
|
|
await page.getByLabel('Send immediately').check();
|
|
|
|
// Send
|
|
await page.click('text=Send Broadcast');
|
|
|
|
// Confirm dialog
|
|
await page.click('text=Confirm');
|
|
|
|
await expect(page.getByText('Broadcast sent')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('clones existing broadcast', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
|
|
// Find first broadcast row and click clone
|
|
const firstRow = page.locator('table tbody tr').first();
|
|
await firstRow.getByRole('button', { name: 'Clone' }).click();
|
|
|
|
// Should be on new broadcast page with pre-filled data
|
|
await expect(page).toHaveURL(/.*broadcasts\/new/);
|
|
await expect(page.getByLabel('Title')).not.toHaveValue('');
|
|
});
|
|
|
|
test('pauses and resumes broadcast', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
|
|
// Find a sending broadcast
|
|
const sendingRow = page.locator('tr:has-text("Sending")').first();
|
|
|
|
if (await sendingRow.isVisible().catch(() => false)) {
|
|
await sendingRow.getByRole('button', { name: 'Pause' }).click();
|
|
await expect(page.getByText('Broadcast paused')).toBeVisible();
|
|
|
|
// Resume
|
|
await sendingRow.getByRole('button', { name: 'Resume' }).click();
|
|
await expect(page.getByText('Broadcast resumed')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('views broadcast metrics', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
|
|
// Click on first broadcast title
|
|
await page.locator('table tbody tr td:first-child a').first().click();
|
|
|
|
// Should show detail page
|
|
await expect(page.getByText('Metrics')).toBeVisible();
|
|
await expect(page.getByText('Targeted')).toBeVisible();
|
|
await expect(page.getByText('Sent')).toBeVisible();
|
|
await expect(page.getByText('Delivered')).toBeVisible();
|
|
await expect(page.getByText('Opened')).toBeVisible();
|
|
await expect(page.getByText('Clicked')).toBeVisible();
|
|
});
|
|
|
|
test('deletes broadcast', async ({ page }) => {
|
|
await page.click('text=Broadcasts');
|
|
|
|
// Find a draft broadcast to delete
|
|
const draftRow = page.locator('tr:has-text("Draft")').first();
|
|
|
|
if (await draftRow.isVisible().catch(() => false)) {
|
|
await draftRow.getByRole('button', { name: 'Delete' }).click();
|
|
|
|
// Confirm delete
|
|
await page.click('text=Confirm Delete');
|
|
|
|
await expect(page.getByText('Broadcast deleted')).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Surveys Admin', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
});
|
|
|
|
test('navigates to surveys page', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
await expect(page.getByText('Surveys')).toBeVisible();
|
|
await expect(page.getByText('Create survey')).toBeVisible();
|
|
});
|
|
|
|
test('shows survey list', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
|
|
// Check table headers
|
|
await expect(page.getByText('Title')).toBeVisible();
|
|
await expect(page.getByText('Status')).toBeVisible();
|
|
await expect(page.getByText('Responses')).toBeVisible();
|
|
await expect(page.getByText('Completion Rate')).toBeVisible();
|
|
});
|
|
|
|
test('creates new survey with NPS question', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
await page.click('text=Create survey');
|
|
|
|
// Should navigate to builder page
|
|
await expect(page).toHaveURL(/.*surveys\/new/);
|
|
|
|
// Fill basic info
|
|
await page.getByLabel('Survey Title').fill('NPS Survey Test');
|
|
await page.getByLabel('Description').fill('How likely are you to recommend us?');
|
|
|
|
// Add NPS question
|
|
await page.click('text=Add Question');
|
|
await page.selectOption('select[name="questionType"]', 'nps');
|
|
await page.getByLabel('Question Text').fill('How likely are you to recommend us to a friend?');
|
|
await page.click('text=Add');
|
|
|
|
// Add multiple choice question
|
|
await page.click('text=Add Question');
|
|
await page.selectOption('select[name="questionType"]', 'single_choice');
|
|
await page.getByLabel('Question Text').fill('What is your primary use case?');
|
|
|
|
// Add options
|
|
await page.click('text=Add Option');
|
|
await page.getByPlaceholder('Option text').fill('Voice dictation');
|
|
await page.click('text=Add Option');
|
|
await page.getByPlaceholder('Option text').nth(1).fill('Keyboard');
|
|
|
|
await page.click('text=Add');
|
|
|
|
// Save survey
|
|
await page.click('text=Save Survey');
|
|
|
|
await expect(page.getByText('Survey saved')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('creates survey with conditional logic', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
await page.click('text=Create survey');
|
|
|
|
// Basic info
|
|
await page.getByLabel('Survey Title').fill('Conditional Survey');
|
|
|
|
// Add first question (NPS)
|
|
await page.click('text=Add Question');
|
|
await page.selectOption('select[name="questionType"]', 'nps');
|
|
await page.getByLabel('Question Text').fill('Rate your experience');
|
|
await page.click('text=Add');
|
|
|
|
// Add conditional follow-up question
|
|
await page.click('text=Add Question');
|
|
await page.selectOption('select[name="questionType"]', 'text_long');
|
|
await page.getByLabel('Question Text').fill('What can we improve?');
|
|
|
|
// Set conditional logic
|
|
await page.click('text=Conditional Logic');
|
|
await page.selectOption('select[name="conditionQuestion"]', 'q1');
|
|
await page.selectOption('select[name="conditionOperator"]', 'not_equals');
|
|
await page.fill('input[name="conditionValue"]', '9,10');
|
|
|
|
await page.click('text=Add');
|
|
|
|
// Save
|
|
await page.click('text=Save Survey');
|
|
await expect(page.getByText('Survey saved')).toBeVisible();
|
|
});
|
|
|
|
test('activates and pauses survey', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
|
|
// Find a draft survey
|
|
const draftRow = page.locator('tr:has-text("Draft")').first();
|
|
|
|
if (await draftRow.isVisible().catch(() => false)) {
|
|
// Click activate
|
|
await draftRow.getByRole('button', { name: 'Activate' }).click();
|
|
await expect(page.getByText('Survey activated')).toBeVisible();
|
|
|
|
// Find the now-active survey and pause it
|
|
const activeRow = page.locator('tr:has-text("Active")').first();
|
|
await activeRow.getByRole('button', { name: 'Pause' }).click();
|
|
await expect(page.getByText('Survey paused')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('views survey metrics', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
|
|
// Click on first survey
|
|
await page.locator('table tbody tr td:first-child a').first().click();
|
|
|
|
// Should show detail page with tabs
|
|
await expect(page.getByText('Overview')).toBeVisible();
|
|
await expect(page.getByText('Questions')).toBeVisible();
|
|
await expect(page.getByText('Responses')).toBeVisible();
|
|
await expect(page.getByText('Analytics')).toBeVisible();
|
|
|
|
// Click Analytics tab
|
|
await page.click('text=Analytics');
|
|
await expect(page.getByText('Completion Rate')).toBeVisible();
|
|
await expect(page.getByText('Average Time')).toBeVisible();
|
|
});
|
|
|
|
test('exports survey responses', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
|
|
// Click on first survey
|
|
await page.locator('table tbody tr td:first-child a').first().click();
|
|
|
|
// Go to Responses tab
|
|
await page.click('text=Responses');
|
|
|
|
// Click export
|
|
await page.click('text=Export');
|
|
|
|
// Wait for download or success message
|
|
await expect(page.getByText('Export started')).toBeVisible();
|
|
});
|
|
|
|
test('sets survey incentive', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
await page.click('text=Create survey');
|
|
|
|
// Basic info
|
|
await page.getByLabel('Survey Title').fill('Incentivized Survey');
|
|
|
|
// Go to Settings tab
|
|
await page.click('text=Settings');
|
|
|
|
// Enable incentive
|
|
await page.getByLabel('Enable Incentive').check();
|
|
await page.selectOption('select[name="incentiveType"]', 'pro_days');
|
|
await page.getByLabel('Incentive Amount').fill('7');
|
|
|
|
// Add question
|
|
await page.click('text=Questions');
|
|
await page.click('text=Add Question');
|
|
await page.selectOption('select[name="questionType"]', 'nps');
|
|
await page.getByLabel('Question Text').fill('How likely are you to recommend us?');
|
|
await page.click('text=Add');
|
|
|
|
// Save
|
|
await page.click('text=Save Survey');
|
|
await expect(page.getByText('Survey saved')).toBeVisible();
|
|
});
|
|
|
|
test('configures survey targeting', async ({ page }) => {
|
|
await page.click('text=Surveys');
|
|
await page.click('text=Create survey');
|
|
|
|
// Basic info
|
|
await page.getByLabel('Survey Title').fill('Targeted Survey');
|
|
|
|
// Go to Targeting tab
|
|
await page.click('text=Targeting');
|
|
|
|
// Select platforms
|
|
await page.getByLabel('iOS').check();
|
|
await page.getByLabel('Android').check();
|
|
|
|
// Select user segments
|
|
await page.getByLabel('Active Users').check();
|
|
await page.getByLabel('Pro Users').check();
|
|
|
|
// Set rollout
|
|
await page.getByLabel('Percentage Rollout').fill('25');
|
|
|
|
// Add question
|
|
await page.click('text=Questions');
|
|
await page.click('text=Add Question');
|
|
await page.selectOption('select[name="questionType"]', 'rating');
|
|
await page.getByLabel('Question Text').fill('Rate the app');
|
|
await page.click('text=Add');
|
|
|
|
// Save
|
|
await page.click('text=Save Survey');
|
|
await expect(page.getByText('Survey saved')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Broadcast & Survey Integration', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
});
|
|
|
|
test('sidebar navigation shows both sections', async ({ page }) => {
|
|
await expect(page.getByText('Broadcasts')).toBeVisible();
|
|
await expect(page.getByText('Surveys')).toBeVisible();
|
|
});
|
|
|
|
test('can navigate between broadcasts and surveys', async ({ page }) => {
|
|
// Go to broadcasts
|
|
await page.click('text=Broadcasts');
|
|
await expect(page.getByText('Create broadcast')).toBeVisible();
|
|
|
|
// Go to surveys
|
|
await page.click('text=Surveys');
|
|
await expect(page.getByText('Create survey')).toBeVisible();
|
|
|
|
// Back to broadcasts
|
|
await page.click('text=Broadcasts');
|
|
await expect(page.getByText('Create broadcast')).toBeVisible();
|
|
});
|
|
});
|