Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
151 changes: 151 additions & 0 deletions graphql/playwright-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# @constructive-io/playwright-test

Constructive Playwright Testing with HTTP server support for end-to-end testing.

This package extends `@constructive-io/graphql-test` to provide an actual HTTP server for Playwright and other E2E testing frameworks. It creates isolated test databases and starts a GraphQL server that bypasses domain routing, making it perfect for integration testing.

## Installation

```bash
npm install @constructive-io/playwright-test
```

## Usage

### Basic Usage

```typescript
import { getConnectionsWithServer, seed } from '@constructive-io/playwright-test';

describe('E2E Tests', () => {
let teardown: () => Promise<void>;
let serverUrl: string;

beforeAll(async () => {
const connections = await getConnectionsWithServer({
schemas: ['public', 'app_public'],
authRole: 'anonymous'
}, [seed.pgpm({ extensions: ['my-extension'] })]);

teardown = connections.teardown;
serverUrl = connections.server.graphqlUrl;
});

afterAll(async () => {
await teardown();
});

it('should work with Playwright', async () => {
// Use serverUrl in your Playwright tests
// e.g., await page.goto(serverUrl);
});
});
```

### With Playwright

```typescript
import { test, expect } from '@playwright/test';
import { getConnectionsWithServer } from '@constructive-io/playwright-test';

let connections: Awaited<ReturnType<typeof getConnectionsWithServer>>;

test.beforeAll(async () => {
connections = await getConnectionsWithServer({
schemas: ['services_public', 'app_public'],
authRole: 'anonymous',
server: {
port: 5555,
host: 'localhost'
}
});
});

test.afterAll(async () => {
await connections.teardown();
});

test('GraphQL API is accessible', async ({ page }) => {
const response = await page.request.post(connections.server.graphqlUrl, {
data: {
query: '{ __typename }'
}
});
expect(response.ok()).toBeTruthy();
});
```

### Direct Query Access

You can also execute GraphQL queries directly without going through HTTP:

```typescript
const { query, server, teardown } = await getConnectionsWithServer({
schemas: ['public'],
authRole: 'anonymous'
});

// Direct query (bypasses HTTP)
const result = await query(`
query {
allUsers {
nodes {
id
name
}
}
}
`);

// HTTP endpoint for Playwright
console.log(server.graphqlUrl); // http://localhost:5555/graphql
```

## API

### getConnectionsWithServer(input, seedAdapters?)

Creates database connections and starts an HTTP server for testing.

**Parameters:**
- `input.schemas` - Array of PostgreSQL schemas to expose
- `input.authRole` - Default authentication role (e.g., 'anonymous', 'authenticated')
- `input.server.port` - Port to run the server on (defaults to random available port)
- `input.server.host` - Host to bind to (defaults to 'localhost')
- `input.graphile` - Optional Graphile configuration overrides
- `seedAdapters` - Optional array of seed adapters for database setup

**Returns:**
- `pg` - PostgreSQL client for direct database access
- `db` - Database client for test operations
- `server` - Server info including `url`, `graphqlUrl`, `port`, `host`, and `stop()`
- `query` - GraphQL query function (positional API)
- `teardown` - Cleanup function to stop server and drop test database

### getConnectionsWithServerObject(input, seedAdapters?)

Same as `getConnectionsWithServer` but uses object-based query API.

### getConnectionsWithServerUnwrapped(input, seedAdapters?)

Same as `getConnectionsWithServer` but throws on GraphQL errors instead of returning them.

### createTestServer(opts, serverOpts?)

Low-level function to create just the HTTP server without database setup.

## How It Works

1. Creates an isolated test database using `pgsql-test`
2. Starts an Express server with Constructive GraphQL middleware
3. Configures `enableServicesApi: false` to bypass domain/subdomain routing
4. Exposes the specified schemas directly via the GraphQL endpoint
5. Returns the server URL for Playwright to connect to
6. Provides a teardown function that stops the server and cleans up the database

## Configuration

The server is configured with `enableServicesApi: false`, which means:
- No domain/subdomain routing is required
- Schemas are exposed directly based on the `schemas` parameter
- Perfect for isolated testing without complex domain setup
39 changes: 39 additions & 0 deletions graphql/playwright-test/__tests__/server.playwright.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { test, expect } from '@playwright/test';
import { join } from 'path';
import { seed } from 'pgsql-test';

import { getConnectionsWithServer } from '../src';

const sql = (f: string) => join(__dirname, '/../sql', f);

test.describe('playwright-test server', () => {
test('GraphQL server responds to queries via HTTP and direct query', async ({ request }) => {
const { server, query, teardown } = await getConnectionsWithServer(
{
useRoot: true,
schemas: ['app_public'],
authRole: 'postgres'
},
[seed.sqlfile([sql('test.sql')])]
);

try {
// Test 1: HTTP introspection query
const introspectionResponse = await request.post(server.graphqlUrl, {
data: { query: '{ __typename }' },
headers: { 'Content-Type': 'application/json' }
});
expect(introspectionResponse.ok()).toBeTruthy();
const introspectionJson = await introspectionResponse.json();
expect(introspectionJson.data).toBeDefined();
expect(introspectionJson.data.__typename).toBe('Query');

// Test 2: Direct query function works alongside HTTP server
const directResult = await query('{ __typename }');
expect(directResult.data).toBeDefined();
expect(directResult.data.__typename).toBe('Query');
} finally {
await teardown();
}
});
});
18 changes: 18 additions & 0 deletions graphql/playwright-test/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
babelConfig: false,
tsconfig: 'tsconfig.json'
}
]
},
transformIgnorePatterns: [`/node_modules/*`],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
modulePathIgnorePatterns: ['dist/*']
};
58 changes: 58 additions & 0 deletions graphql/playwright-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@constructive-io/playwright-test",
"version": "0.1.0",
"author": "Constructive <developers@constructive.io>",
"description": "Constructive Playwright Testing with HTTP server support",
"main": "index.js",
"module": "esm/index.js",
"types": "index.d.ts",
"homepage": "https://github.com/constructive-io/constructive",
"license": "MIT",
"publishConfig": {
"access": "public",
"directory": "dist"
},
"repository": {
"type": "git",
"url": "https://github.com/constructive-io/constructive"
},
"bugs": {
"url": "https://github.com/constructive-io/constructive/issues"
},
"scripts": {
"clean": "makage clean",
"prepack": "npm run build",
"build": "makage build",
"build:dev": "makage build --dev",
"lint": "eslint . --fix",
"test": "playwright test",
"test:watch": "jest --watch"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@types/express": "^5.0.6",
"@types/pg": "^8.16.0",
"makage": "^0.1.10"
},
"dependencies": {
"@constructive-io/graphql-env": "workspace:^",
"@constructive-io/graphql-server": "workspace:^",
"@constructive-io/graphql-test": "workspace:^",
"@constructive-io/graphql-types": "workspace:^",
"@pgpmjs/types": "workspace:^",
"express": "^5.2.1",
"graphile-test": "workspace:^",
"pg": "^8.16.3",
"pg-cache": "workspace:^",
"pgsql-test": "workspace:^"
},
"keywords": [
"testing",
"playwright",
"graphql",
"graphile",
"constructive",
"e2e",
"integration"
]
}
17 changes: 17 additions & 0 deletions graphql/playwright-test/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: './__tests__',
testMatch: '**/*.playwright.test.ts',
timeout: 60000,
retries: 0,
use: {
baseURL: 'http://localhost',
},
projects: [
{
name: 'api',
testMatch: '**/*.playwright.test.ts',
},
],
});
12 changes: 12 additions & 0 deletions graphql/playwright-test/sql/test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
BEGIN;
CREATE EXTENSION IF NOT EXISTS citext;
DROP SCHEMA IF EXISTS app_public CASCADE;
CREATE SCHEMA app_public;
CREATE TABLE app_public.users (
id serial PRIMARY KEY,
username citext NOT NULL,
UNIQUE (username),
CHECK (length(username) < 127)
);
INSERT INTO app_public.users (username) VALUES ('testuser');
COMMIT;
Loading