Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ DATABASE_URL=postgresql://devuser:changeme@localhost:5433/devdb?schema=public

# Project
PROJECT_NAME="Portabase"
PROJECT_DESCRIPTION="Portabase is a powerful database manager"
PROJECT_URL=http://localhost:8887
PROJECT_SECRET=

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
version:
required: true
type: string
ref:
required: true
type: string
Comment thread
KillianLarcher marked this conversation as resolved.
Resolved
image_name:
required: false
type: string
Expand Down Expand Up @@ -39,6 +42,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down
91 changes: 91 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: End-to-end testing

on:
pull_request:
branches: [main]

jobs:
build-and-test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:17
Comment thread
KillianLarcher marked this conversation as resolved.
Resolved
env:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: changeme
POSTGRES_DB: devdb
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U devuser -d devdb"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/dockerfile/Dockerfile
push: false
load: true
tags: portabase/portabase:test

- name: Run app container
run: |
docker run -d --name myapp \
-p 8887:8887 \
-e DATABASE_URL=postgres://devuser:changeme@localhost:5432/devdb \
-e NODE_ENV=production \
-e PROJECT_URL=http://localhost:8887 \
portabase/portabase:test
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

- name: Wait for app port to be listening
run: |
for i in {1..40}; do
if nc -z localhost 8887; then
echo "Port 8887 is open → app is likely listening"
exit 0
fi
echo "Waiting for port 8887... ($i/40)"
sleep 2
done
echo "Timeout: port 8887 never opened"
docker logs myapp
exit 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps

- name: Run Playwright tests
run: pnpm exec playwright test
env:
BASE_URL: http://localhost:8887

- name: Upload report if failed
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7

- name: Cleanup
if: always()
run: docker stop myapp && docker rm myapp || true
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ jobs:
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.create-release.outputs.version }}
ref: ${{ needs.create-release.outputs.version }}
add_latest: true
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,10 @@ private/keys/*
seeds/pocket-id/*.db

certificates

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
4 changes: 2 additions & 2 deletions app/api/agent/[agentId]/restore/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export async function POST(
await sendNotificationsBackupRestore(database, body.status == "failed" ? "error_restore" : "success_restore");

const response = {
message: true,
details: "Restoration successfully updated"
status: true,
message: "Restoration successfully updated"
}


Expand Down
135 changes: 135 additions & 0 deletions e2e/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {test, expect, Page} from '@playwright/test';


type UserCredentials = {
username: string
email: string
password: string
}


const users: Record<string, UserCredentials> = {
admin: {username: 'Admin', email: 'admin@example.com', password: 'testPASS123456!'},
normal: {username: 'John Doe', email: 'john.doe@example.com', password: 'testPASS123456!'},
}
Comment thread
KillianLarcher marked this conversation as resolved.
Resolved

async function register(page: Page, name: string, email: string, password: string, confirmPassword: string) {
await page.locator('input[name="name"]').fill(name)
await page.locator('input[name="email"]').fill(email)
await page.locator('input[name="password"]').fill(password)
await page.locator('input[name="confirmPassword"]').fill(confirmPassword)

await page.click('button[type="submit"]')
}

async function login(page: Page, email: string, password: string) {
await page.locator('input[name="email"]').fill(email)
await page.locator('input[name="password"]').fill(password)

await page.locator('button:has-text("Login")').click()
}

async function logout() {
Comment thread
KillianLarcher marked this conversation as resolved.
Dismissed
//TODO
}

test.describe.serial('User signup and login flows', () => {

test('Redirect to login if not connected', async ({page}) => {
await page.goto('/dashboard/projects');
await expect(page).toHaveURL("login?redirect=%2Fdashboard%2Fprojects");
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

test('Password too short', async ({page}) => {
await page.goto('')
await page.click('text=Sign up')
await expect(page).toHaveURL('/register')

let password = '123456'
await register(page, users["admin"].username, users["admin"].email, password, password)

const toast = page.locator('text=Must have at least 8 character')
await expect(toast).toBeVisible()
})

test('Password too simple', async ({page}) => {
await page.goto('')
await page.click('text=Sign up')
await expect(page).toHaveURL('/register')

let password = '12345678'
await register(page, users["admin"].username, users["admin"].email, password, password)

const toast = page.locator('text=Your password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.')
await expect(toast).toBeVisible()
})

test('Password and confirm password mismatch', async ({page}) => {
await page.goto('/')
await page.click('text=Sign up')
await expect(page).toHaveURL('/register')

let password = 'testPASS123456!'
let confirmPassword = 'testPASS123456!!'
await register(page, users["admin"].username, users["admin"].email, password, confirmPassword)

const toast = page.locator('text=The passwords did not match')
await expect(toast).toBeVisible()
})

test('Successful register for admin', async ({page}) => {
await page.goto('/')

await page.click('text=Sign up')
await expect(page).toHaveURL('/register')

await register(page, users["admin"].username, users["admin"].email, users["admin"].password, users["admin"].password)

await expect(page).toHaveURL('/login')
})

test('User already exists.', async ({page}) => {
await page.goto('/')

await page.click('text=Sign up')
await expect(page).toHaveURL('/register')

await register(page, users["admin"].username, users["admin"].email, users["admin"].password, users["admin"].password)

const toast = page.locator('text=User already exists. Use another email.')
await expect(toast).toBeVisible()
})

test('Successful register for normal', async ({page}) => {
await page.goto('/')

await page.click('text=Sign up')
await expect(page).toHaveURL('/register')

await register(page, users["normal"].username, users["normal"].email, users["normal"].password, users["normal"].password)

await expect(page).toHaveURL('login')
})

test('Failed login because account not active', async ({page}) => {
await page.goto('/login')
await login(page, users["normal"].email, users["normal"].password)

const toast = page.locator('text=Your account is not active.')
await expect(toast).toBeVisible()
})

test('Successful login', async ({page}) => {
await page.goto('login')
await login(page, users["admin"].email, users["admin"].password)

await expect(page).toHaveURL('/dashboard')
})

})


// test('Successful logout', async ({page}) => {
// await logout()
// await expect(page).toHaveURL('/login')
// })
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
},
"devDependencies": {
"@iconify/react": "^6.0.2",
"@playwright/test": "^1.58.2",
"@react-email/preview-server": "4.3.2",
"@react-email/render": "^2.0.4",
"@release-it/bumper": "^7.0.5",
Expand Down
79 changes: 79 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
baseURL: 'http://localhost:8887',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
Comment on lines +27 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using environment variable for baseURL.

The baseURL is hardcoded to http://localhost:8887, but the GitHub Actions workflow passes a BASE_URL environment variable. Using the env var with a fallback would make the configuration more flexible for different environments.

♻️ Proposed fix
   use: {
     /* Base URL to use in actions like `await page.goto('')`. */
-    baseURL: 'http://localhost:8887',
+    baseURL: process.env.BASE_URL || 'http://localhost:8887',

     /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
     trace: 'on-first-retry',
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use: {
/* Base URL to use in actions like `await page.goto('')`. */
baseURL: 'http://localhost:8887',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
use: {
/* Base URL to use in actions like `await page.goto('')`. */
baseURL: process.env.BASE_URL || 'http://localhost:8887',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playwright.config.ts` around lines 27 - 33, The Playwright config currently
hardcodes use.baseURL to 'http://localhost:8887'; change it to read from the
environment with a fallback (e.g., process.env.BASE_URL ||
'http://localhost:8887') so CI/workflows can override the URL; update the use
block inside the exported config (the use object in playwright.config.ts) to set
baseURL from process.env.BASE_URL and ensure the value is a string-compatible
fallback.


/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'docker compose up -d && make up',
// url: 'http://localhost:8887',
// reuseExistingServer: !process.env.CI,
// },
});
Loading
Loading