Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
84 changes: 84 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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: 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@postgres: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: Setup Node + Playwright
uses: actions/setup-node@v4
with:
node-version: 22

- run: npm ci
- run: npx playwright install --with-deps
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

- name: Run Playwright tests
run: npx playwright test
env:
BASE_URL: http://localhost:8887
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

- 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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ private/keys/*
seeds/pocket-id/*.db

certificates

# Playwright
node_modules/
/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
136 changes: 136 additions & 0 deletions e2e/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {test, expect, Page} from '@playwright/test';


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


const users: Record<string, UserCredentials> = {
admin: {username: 'Admin', email: 'admin5@example.com', password: 'testPASS123456!'},
normal: {username: 'John Doe', email: 'john5@example.comm', 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.click('button[type="submit"]')
await page.locator('button:has-text("Login")').click()
// await page.click('text=Login')
}

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

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')
})

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.

⚠️ Potential issue | 🔴 Critical

Test will fail: logout() is not implemented.

The logout function on lines 34-35 is empty and does nothing. Additionally, this test doesn't navigate to a logged-in state before calling logout(), so the URL assertion will fail.

🐛 Proposed fix
-async function logout() {
-}
+async function logout(page: Page) {
+  // Navigate to a page with logout functionality
+  await page.goto('/dashboard');
+  // Click logout button - adjust selector as needed
+  await page.locator('button:has-text("Logout")').click();
+}

-test('Successful logout', async ({page}) => {
-    await logout()
-    await expect(page).toHaveURL('login')
-})
+test('Successful logout', async ({page}) => {
+    // First, log in
+    await page.goto('login');
+    await login(page, users["admin"].email, users["admin"].password);
+    await expect(page).toHaveURL('dashboard');
+    
+    // Then log out
+    await logout(page);
+    await expect(page).toHaveURL(/login/);
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/auth.spec.ts` around lines 133 - 136, The test fails because logout() is
a no-op and the test never ensures a logged-in state; implement logout() to
perform the actual sign-out flow (e.g., open the user menu and click the
"Logout" button and wait for navigation) and update the "Successful logout" test
to ensure an authenticated state before calling logout() (either call the
existing login() helper or set the auth cookie/session), then call logout() and
assert the page navigates to 'login'. Locate and modify the logout() function
and the test named 'Successful logout' to add these steps.

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