mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
chore(e2e): add GitHub Actions workflow for Playwright UI E2E tests
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
name: UI - E2E Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- "v5.*"
|
||||
paths:
|
||||
- 'ui/**'
|
||||
|
||||
env:
|
||||
SERVICES_TO_START: "api-dev postgres valkey worker-beat worker-dev"
|
||||
DOCKER_COMPOSE_FILE: "docker-compose-dev.yml"
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './ui/package-lock.json'
|
||||
|
||||
# - name: Cache Playwright Browsers
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: ~/.cache/ms-playwright
|
||||
# key: playwright-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# playwright-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
working-directory: ./ui
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
working-directory: ./ui
|
||||
|
||||
- name: Set up Docker Compose
|
||||
uses: docker/setup-compose-action@364cc21a5de5b1ee4a7f5f9d3fa374ce0ccde746 #v1.2.0
|
||||
- name: Start Docker Compose
|
||||
run: docker compose -f ${DOCKER_COMPOSE_FILE} up -d ${SERVICES_TO_START}
|
||||
|
||||
- name: Wait for API to be ready
|
||||
run: |
|
||||
for i in {1..30}; do
|
||||
if curl -s http://localhost:8000/api/v1; then
|
||||
echo "API is up!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for API..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
working-directory: ./ui
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: ./ui/playwright-report
|
||||
|
||||
- name: Upload Playwright videos
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-videos
|
||||
path: ./ui/test-results/**/*.webm
|
||||
|
||||
- name: Docker Compose Down
|
||||
if: always()
|
||||
run: docker compose -f ${DOCKER_COMPOSE_FILE} down
|
||||
@@ -34,3 +34,7 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# Playwright test artifacts
|
||||
playwright-report/
|
||||
test-results/
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@
|
||||
"format:check": "./node_modules/.bin/prettier --check ./app",
|
||||
"format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app",
|
||||
"prepare": "husky",
|
||||
"test:e2e": "playwright test"
|
||||
"test:e2e": "npx playwright test && npx playwright show-report"
|
||||
},
|
||||
"overrides": {
|
||||
"@react-types/shared": "3.26.0"
|
||||
|
||||
+14
-17
@@ -1,38 +1,35 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const isLocal = process.env.LOCAL === "true";
|
||||
|
||||
export default defineConfig({
|
||||
timeout: 60 * 1000,
|
||||
testDir: "./tests/e2e",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
forbidOnly: !isLocal,
|
||||
retries: isLocal ? 0 : 2,
|
||||
workers: isLocal ? undefined : 1,
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
globalSetup: require.resolve("./tests/e2e/global-setup"),
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:3000",
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
// Capture screenshot on failure
|
||||
screenshot: "only-on-failure",
|
||||
// Record video for failed tests
|
||||
video: "retain-on-failure",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
// {
|
||||
// name: 'chromium',
|
||||
// use: { ...devices['Desktop Chrome'] },
|
||||
// },
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
@@ -63,7 +60,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: "npm run dev",
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: true,
|
||||
reuseExistingServer: !isLocal,
|
||||
timeout: 300 * 1000, // 5 minute
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Playwright E2E Testing
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
Playwright is already set up. To install dependencies:
|
||||
|
||||
```bash
|
||||
cd ui
|
||||
npm install
|
||||
|
||||
# Run all tests (headless)
|
||||
npm run test:e2e
|
||||
|
||||
# Run specific file (headless)
|
||||
npx playwright test tests/e2e/root.spec.ts
|
||||
|
||||
# Run all tests with UI (headed mode)
|
||||
npx playwright test --headed
|
||||
|
||||
# Run specific file with UI (headed mode)
|
||||
npx playwright test tests/e2e/root.spec.ts --headed
|
||||
|
||||
# Open the HTML report from last test run
|
||||
npx playwright show-report
|
||||
@@ -1,36 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Test credentials from environment variables
|
||||
const TEST_USER_EMAIL = process.env.TEST_USER_EMAIL || 'dev@prowler.com';
|
||||
const TEST_USER_PASSWORD = process.env.TEST_USER_PASSWORD || 'thisisapassword123';
|
||||
|
||||
test('should render login page with expected elements', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await expect(page.locator('input[name="email"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="password"]')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Log In' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show error for invalid login', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[name="email"]', 'test@example.com');
|
||||
await page.fill('input[name="password"]', 'password');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.getByText(/invalid email or password/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should login and show "No results" message on Findings page search', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
const title = await page.title();
|
||||
|
||||
await page.fill('input[name="email"]', TEST_USER_EMAIL);
|
||||
await page.fill('input[name="password"]', TEST_USER_PASSWORD);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL('/');
|
||||
await page.goto('/findings');
|
||||
await page.waitForSelector('input[type="text"]');
|
||||
await page.fill('input[type="text"]', 'findingsTest');
|
||||
await page.waitForSelector('table');
|
||||
await expect(page.getByText('No results.')).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { FullConfig } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
const isLocal = process.env.LOCAL === 'true';
|
||||
|
||||
if (isLocal) {
|
||||
console.log('LOCAL=true detected — skipping Docker startup.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Start Docker containers (for CI or non-local runs)
|
||||
console.log('Starting Docker containers...');
|
||||
execSync(
|
||||
'docker-compose -f ../docker-compose-dev.yml up -d --build api-dev postgres valkey worker-beat worker-dev',
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
// Wait for services to be ready
|
||||
console.log('Waiting for services to be ready...');
|
||||
await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait 30 seconds
|
||||
|
||||
// Register cleanup function
|
||||
process.on('exit', () => {
|
||||
console.log('Cleaning up containers...');
|
||||
execSync('docker-compose -f ../docker-compose-dev.yml down', { stdio: 'inherit' });
|
||||
});
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('Unauthenticated users are redirected to sign-in and can navigate to sign-up', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveURL(/\/sign-in/);
|
||||
await expect(page.getByText('Sign In')).toBeVisible();
|
||||
|
||||
await page.getByRole('link', { name: /sign up/i }).click();
|
||||
await expect(page).toHaveURL(/\/sign-up/);
|
||||
});
|
||||
Reference in New Issue
Block a user