@ochorocho/playwright-db-connector 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fixtures.ts","../src/cleanup-tracker.ts","../src/sql-loader.ts","../src/csv-loader.ts","../src/errors.ts","../src/database-connector.ts","../src/driver-detector.ts","../src/matchers.ts"],"sourcesContent":["import { test as base } from '@playwright/test';\nimport knex from 'knex';\nimport type { Knex } from 'knex';\nimport type { DbTestFixtures, DbWorkerFixtures, DbConnectorConfig } from './types.js';\nimport { DatabaseConnectorImpl } from './database-connector.js';\nimport { CleanupTracker } from './cleanup-tracker.js';\nimport { SqlLoader } from './sql-loader.js';\nimport { CsvLoader } from './csv-loader.js';\nimport { detectDriver } from './driver-detector.js';\n\nexport const test = base.extend<DbTestFixtures, DbWorkerFixtures>({\n // Worker-scoped option: database configuration\n dbConfig: [\n {\n client: 'better-sqlite3',\n connection: { filename: ':memory:' },\n } as DbConnectorConfig,\n { option: true, scope: 'worker' },\n ],\n\n // Worker-scoped: knex connection pool (internal)\n _dbKnexPool: [\n async ({ dbConfig }, use) => {\n detectDriver(dbConfig.client);\n\n const isSqlite = dbConfig.client === 'better-sqlite3' || dbConfig.client === 'sqlite3';\n\n const knexConfig: Knex.Config = {\n client: dbConfig.client,\n connection: dbConfig.connection as Knex.StaticConnectionConfig,\n pool: isSqlite ? { min: 1, max: 1 } : { min: 0, max: 5, ...dbConfig.pool },\n useNullAsDefault: isSqlite,\n acquireConnectionTimeout: 5000,\n ...dbConfig.knexConfig,\n };\n\n const db = knex(knexConfig);\n\n try {\n // Execute SQL seed files if configured\n if (dbConfig.seedFiles) {\n const files = Array.isArray(dbConfig.seedFiles)\n ? dbConfig.seedFiles\n : [dbConfig.seedFiles];\n for (const file of files) {\n await SqlLoader.executeFile(db, file);\n }\n }\n\n // Import CSV seed files if configured\n if (dbConfig.seedCsvFiles) {\n const files = Array.isArray(dbConfig.seedCsvFiles)\n ? dbConfig.seedCsvFiles\n : [dbConfig.seedCsvFiles];\n for (const file of files) {\n await CsvLoader.importFile(db, file);\n }\n }\n } catch (error) {\n await db.destroy();\n throw error;\n }\n\n await use(db);\n\n await db.destroy();\n },\n { scope: 'worker' },\n ],\n\n // Test-scoped: the `db` fixture users interact with\n db: async ({ _dbKnexPool, dbConfig }, use) => {\n const strategy = dbConfig.cleanupStrategy ?? 'transaction';\n const tracker = new CleanupTracker();\n\n if (strategy === 'transaction') {\n const trx = await _dbKnexPool.transaction();\n const connector = new DatabaseConnectorImpl(trx, tracker, dbConfig.client, dbConfig.primaryKeyColumn);\n try {\n await use(connector);\n } finally {\n // Always rollback — this is the isolation mechanism\n if (!trx.isCompleted()) {\n try {\n await trx.rollback();\n } catch (rollbackError) {\n console.error('Transaction rollback failed:', rollbackError);\n }\n }\n }\n } else if (strategy === 'delete' || strategy === 'truncate') {\n const connector = new DatabaseConnectorImpl(_dbKnexPool, tracker, dbConfig.client, dbConfig.primaryKeyColumn);\n try {\n await use(connector);\n } finally {\n if (strategy === 'delete') {\n await tracker.cleanupByDelete(_dbKnexPool);\n } else {\n await tracker.cleanupByTruncate(_dbKnexPool, dbConfig.client);\n }\n }\n } else {\n // strategy === 'none'\n const connector = new DatabaseConnectorImpl(_dbKnexPool, tracker, dbConfig.client, dbConfig.primaryKeyColumn);\n await use(connector);\n }\n },\n});\n","import type { Knex } from 'knex';\nimport type { DatabaseClient, TrackedInsert } from './types.js';\n\nexport class CleanupTracker {\n private inserts: TrackedInsert[] = [];\n\n track(table: string, primaryKey: string, primaryKeyValues: unknown[]): void {\n this.inserts.push({ table, primaryKey, primaryKeyValues });\n }\n\n /**\n * Delete tracked rows in reverse insertion order (respects foreign keys).\n */\n async cleanupByDelete(queryable: Knex): Promise<void> {\n for (const insert of [...this.inserts].reverse()) {\n await queryable(insert.table)\n .whereIn(insert.primaryKey, insert.primaryKeyValues as string[])\n .delete();\n }\n this.clear();\n }\n\n /**\n * Truncate all tables that received inserts. Dialect-aware.\n */\n async cleanupByTruncate(queryable: Knex, client: DatabaseClient): Promise<void> {\n const tables = [...new Set(this.inserts.map((i) => i.table))];\n for (const table of tables) {\n if (client === 'pg') {\n await queryable.raw('TRUNCATE TABLE ?? RESTART IDENTITY CASCADE', [table]);\n } else if (client === 'mysql2') {\n await queryable.raw('SET FOREIGN_KEY_CHECKS = 0');\n await queryable.raw('TRUNCATE TABLE ??', [table]);\n await queryable.raw('SET FOREIGN_KEY_CHECKS = 1');\n } else {\n // SQLite: DELETE all rows + reset autoincrement sequence\n await queryable(table).delete();\n try {\n await queryable.raw('DELETE FROM sqlite_sequence WHERE name = ?', [table]);\n } catch (error) {\n // sqlite_sequence may not exist if no autoincrement columns are used\n if (error instanceof Error && !error.message.includes('no such table')) {\n throw error;\n }\n }\n }\n }\n this.clear();\n }\n\n clear(): void {\n this.inserts = [];\n }\n\n get trackedInserts(): ReadonlyArray<TrackedInsert> {\n return this.inserts;\n }\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Knex } from 'knex';\n\nexport class SqlLoader {\n /**\n * Read a SQL file and execute its statements against the database.\n */\n static async executeFile(db: Knex, filePath: string): Promise<void> {\n const absolutePath = path.resolve(filePath);\n const sql = fs.readFileSync(absolutePath, 'utf-8');\n await SqlLoader.executeStatements(db, sql);\n }\n\n /**\n * Execute a SQL string containing one or more semicolon-separated statements.\n */\n static async executeStatements(db: Knex, sql: string): Promise<void> {\n const statements = SqlLoader.splitStatements(sql);\n for (const statement of statements) {\n const trimmed = statement.trim();\n if (trimmed.length > 0) {\n await db.raw(trimmed);\n }\n }\n }\n\n /**\n * Split a SQL string on semicolons while respecting:\n * - Single-quoted strings ('it''s ok')\n * - Double-quoted identifiers (\"table\")\n * - Single-line comments (-- comment)\n * - Multi-line comments (/* comment *​/)\n */\n static splitStatements(sql: string): string[] {\n const statements: string[] = [];\n let current = '';\n let i = 0;\n\n while (i < sql.length) {\n const char = sql[i];\n const next = sql[i + 1];\n\n // Single-line comment: skip to end of line\n if (char === '-' && next === '-') {\n const eol = sql.indexOf('\\n', i);\n if (eol === -1) {\n current += sql.slice(i);\n i = sql.length;\n } else {\n current += sql.slice(i, eol + 1);\n i = eol + 1;\n }\n continue;\n }\n\n // Multi-line comment: skip to closing */\n if (char === '/' && next === '*') {\n const end = sql.indexOf('*/', i + 2);\n if (end === -1) {\n current += sql.slice(i);\n i = sql.length;\n } else {\n current += sql.slice(i, end + 2);\n i = end + 2;\n }\n continue;\n }\n\n // Single-quoted string: consume until closing quote (handle escaped quotes '')\n if (char === \"'\") {\n current += char;\n i++;\n while (i < sql.length) {\n if (sql[i] === \"'\" && sql[i + 1] === \"'\") {\n current += \"''\";\n i += 2;\n } else if (sql[i] === \"'\") {\n current += \"'\";\n i++;\n break;\n } else {\n current += sql[i];\n i++;\n }\n }\n continue;\n }\n\n // Double-quoted identifier\n if (char === '\"') {\n current += char;\n i++;\n while (i < sql.length) {\n if (sql[i] === '\"' && sql[i + 1] === '\"') {\n current += '\"\"';\n i += 2;\n } else if (sql[i] === '\"') {\n current += '\"';\n i++;\n break;\n } else {\n current += sql[i];\n i++;\n }\n }\n continue;\n }\n\n // Statement separator\n if (char === ';') {\n statements.push(current);\n current = '';\n i++;\n continue;\n }\n\n current += char;\n i++;\n }\n\n // Don't lose the last statement if it lacks a trailing semicolon\n if (current.trim().length > 0) {\n statements.push(current);\n }\n\n return statements;\n }\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Knex } from 'knex';\nimport type { RecordData } from './types.js';\nimport { DatabaseAssertionError } from './errors.js';\n\n/**\n * Parsed representation of a CSV dataset: table name → array of row objects.\n */\nexport type CsvDataSet = Map<string, RecordData[]>;\n\n/**\n * Loads and asserts CSV datasets using the TYPO3 testing framework format.\n *\n * Format:\n * - A row starting with a quoted table name begins a new table section.\n * The remaining values on that row are the column headers.\n * - Subsequent rows (starting with a comma / empty first field) are data rows.\n * - `\\NULL` represents a SQL NULL value.\n * - Multiple tables can appear in one file.\n *\n * Example:\n * ```csv\n * \"users\",\"uid\",\"name\",\"email\",\"active\"\n * ,1,\"Alice\",\"alice@example.com\",1\n * ,2,\"Bob\",\"bob@example.com\",1\n * \"posts\",\"uid\",\"user_id\",\"title\"\n * ,1,1,\"Hello World\"\n * ```\n */\nexport class CsvLoader {\n /**\n * Parse a CSV file into a CsvDataSet.\n */\n static parseFile(filePath: string): CsvDataSet {\n const absolutePath = path.resolve(filePath);\n const content = fs.readFileSync(absolutePath, 'utf-8');\n return CsvLoader.parse(content);\n }\n\n /**\n * Parse CSV content string into a CsvDataSet.\n */\n static parse(content: string): CsvDataSet {\n const dataset: CsvDataSet = new Map();\n const lines = content.split(/\\r?\\n/);\n\n let currentTable = '';\n let columns: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === '') continue;\n\n const fields = CsvLoader.parseLine(trimmed);\n if (fields.length === 0) continue;\n\n const firstField = fields[0];\n\n // A non-empty first field indicates a new table section.\n // The first field is the table name, remaining fields are column headers.\n if (firstField !== '') {\n currentTable = firstField;\n columns = fields.slice(1);\n if (!dataset.has(currentTable)) {\n dataset.set(currentTable, []);\n }\n continue;\n }\n\n // Data row: first field is empty, remaining fields are values\n if (!currentTable || columns.length === 0) continue;\n\n const values = fields.slice(1);\n const row: RecordData = {};\n\n for (let i = 0; i < columns.length; i++) {\n const value = i < values.length ? values[i] : '';\n row[columns[i]] = CsvLoader.convertValue(value);\n }\n\n dataset.get(currentTable)!.push(row);\n }\n\n return dataset;\n }\n\n /**\n * Import a CSV file into the database.\n * Inserts all rows from the dataset into their respective tables.\n */\n static async importFile(db: Knex, filePath: string): Promise<void> {\n const dataset = CsvLoader.parseFile(filePath);\n await CsvLoader.importDataSet(db, dataset);\n }\n\n /**\n * Import a parsed CsvDataSet into the database.\n */\n static async importDataSet(db: Knex, dataset: CsvDataSet): Promise<void> {\n for (const [table, rows] of dataset) {\n if (rows.length === 0) continue;\n await db(table).insert(rows);\n }\n }\n\n /**\n * Assert that the database contains exactly the data described in a CSV file.\n * For each table/row in the CSV, looks up the row by its first column (typically\n * the primary key) and compares all other column values.\n *\n * Throws DatabaseAssertionError with a detailed diff on mismatch.\n */\n static async assertFile(db: Knex, filePath: string): Promise<void> {\n const dataset = CsvLoader.parseFile(filePath);\n await CsvLoader.assertDataSet(db, dataset);\n }\n\n /**\n * Assert that the database matches a parsed CsvDataSet.\n */\n static async assertDataSet(db: Knex, dataset: CsvDataSet): Promise<void> {\n const errors: string[] = [];\n\n for (const [table, expectedRows] of dataset) {\n for (const expectedRow of expectedRows) {\n const columns = Object.keys(expectedRow);\n if (columns.length === 0) continue;\n\n // Use the first column as the lookup key (typically the primary key)\n const lookupColumn = columns[0];\n const lookupValue = expectedRow[lookupColumn];\n\n const actualRow = await db(table)\n .select(columns)\n .where(lookupColumn, lookupValue as string | number)\n .first();\n\n if (!actualRow) {\n errors.push(\n `Table \"${table}\": expected row with ${lookupColumn}=${JSON.stringify(lookupValue)} not found`,\n );\n continue;\n }\n\n for (const col of columns) {\n const expected = expectedRow[col];\n const actual = actualRow[col];\n\n if (expected === null) {\n if (actual !== null) {\n errors.push(\n `Table \"${table}\", ${lookupColumn}=${JSON.stringify(lookupValue)}: ` +\n `column \"${col}\" expected NULL, got ${JSON.stringify(actual)}`,\n );\n }\n } else if (String(expected) !== String(actual)) {\n errors.push(\n `Table \"${table}\", ${lookupColumn}=${JSON.stringify(lookupValue)}: ` +\n `column \"${col}\" expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n );\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new DatabaseAssertionError(\n `CSV dataset assertion failed:\\n${errors.map((e) => ` - ${e}`).join('\\n')}`,\n );\n }\n }\n\n /**\n * Parse a single CSV line into an array of field values.\n * Handles quoted fields and escaped quotes (double-double-quotes).\n */\n static parseLine(line: string): string[] {\n const fields: string[] = [];\n let i = 0;\n\n while (i <= line.length) {\n if (i === line.length) {\n // Trailing comma produces an empty final field\n break;\n }\n\n if (line[i] === '\"') {\n // Quoted field\n i++; // skip opening quote\n let value = '';\n while (i < line.length) {\n if (line[i] === '\"') {\n if (line[i + 1] === '\"') {\n // Escaped quote\n value += '\"';\n i += 2;\n } else {\n // End of quoted field\n i++; // skip closing quote\n break;\n }\n } else {\n value += line[i];\n i++;\n }\n }\n fields.push(value);\n // Skip the comma separator after the field\n if (i < line.length && line[i] === ',') {\n i++;\n }\n } else if (line[i] === ',') {\n // Empty field (consecutive commas or leading comma)\n fields.push('');\n i++;\n } else {\n // Unquoted field\n const commaIndex = line.indexOf(',', i);\n if (commaIndex === -1) {\n fields.push(line.slice(i));\n i = line.length;\n } else {\n fields.push(line.slice(i, commaIndex));\n i = commaIndex + 1;\n }\n }\n }\n\n return fields;\n }\n\n /**\n * Convert a CSV field value to a JavaScript value.\n * - `\\NULL` → null\n * - numeric strings → number\n * - everything else → string\n */\n private static convertValue(value: string): unknown {\n if (value === '\\\\NULL') return null;\n if (value === '') return '';\n\n // Try to parse as number (integer or float)\n if (/^-?\\d+$/.test(value)) return parseInt(value, 10);\n if (/^-?\\d+\\.\\d+$/.test(value)) return parseFloat(value);\n\n return value;\n }\n}\n","export class DatabaseDriverNotFoundError extends Error {\n constructor(client: string) {\n const installCommands: Record<string, string> = {\n pg: 'npm install pg',\n mysql2: 'npm install mysql2',\n 'better-sqlite3': 'npm install better-sqlite3',\n sqlite3: 'npm install sqlite3',\n };\n const installCmd = installCommands[client] ?? `npm install ${client}`;\n super(`Database driver '${client}' is not installed. Install it with: ${installCmd}`);\n this.name = 'DatabaseDriverNotFoundError';\n }\n}\n\nexport class DatabaseAssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DatabaseAssertionError';\n }\n}\n","import type { Knex } from 'knex';\nimport type { DatabaseConnector, WhereCriteria, RecordData, DatabaseClient } from './types.js';\nimport { CleanupTracker } from './cleanup-tracker.js';\nimport { SqlLoader } from './sql-loader.js';\nimport { CsvLoader } from './csv-loader.js';\nimport { DatabaseAssertionError } from './errors.js';\n\nexport class DatabaseConnectorImpl implements DatabaseConnector {\n private readonly queryable: Knex | Knex.Transaction;\n private readonly tracker: CleanupTracker;\n private readonly client: DatabaseClient;\n private readonly primaryKeyColumn: string;\n\n constructor(\n queryable: Knex | Knex.Transaction,\n tracker: CleanupTracker,\n client: DatabaseClient,\n primaryKeyColumn: string = 'id',\n ) {\n this.queryable = queryable;\n this.tracker = tracker;\n this.client = client;\n this.primaryKeyColumn = primaryKeyColumn;\n }\n\n get knex(): Knex {\n return this.queryable as Knex;\n }\n\n // ── Assertions ──\n\n async seeInDatabase(table: string, criteria: WhereCriteria): Promise<void> {\n const count = await this.grabNumRecords(table, criteria);\n if (count === 0) {\n throw new DatabaseAssertionError(\n `Expected at least one record in \"${table}\" matching ${JSON.stringify(criteria)}, but found none`,\n );\n }\n }\n\n async dontSeeInDatabase(table: string, criteria: WhereCriteria): Promise<void> {\n const count = await this.grabNumRecords(table, criteria);\n if (count > 0) {\n throw new DatabaseAssertionError(\n `Expected no records in \"${table}\" matching ${JSON.stringify(criteria)}, but found ${count}`,\n );\n }\n }\n\n async seeNumRecords(\n expectedCount: number,\n table: string,\n criteria?: WhereCriteria,\n ): Promise<void> {\n const actualCount = await this.grabNumRecords(table, criteria);\n if (actualCount !== expectedCount) {\n const criteriaStr = criteria ? ` matching ${JSON.stringify(criteria)}` : '';\n throw new DatabaseAssertionError(\n `Expected ${expectedCount} records in \"${table}\"${criteriaStr}, but found ${actualCount}`,\n );\n }\n }\n\n // ── Retrieval ──\n\n async grabFromDatabase<T = unknown>(\n table: string,\n column: string,\n criteria?: WhereCriteria,\n ): Promise<T | undefined> {\n let qb = this.queryable(table).select(column).first();\n if (criteria) {\n qb = qb.where(criteria);\n }\n const row = await qb;\n return row ? (row[column] as T) : undefined;\n }\n\n async grabColumnFromDatabase<T = unknown>(\n table: string,\n column: string,\n criteria?: WhereCriteria,\n ): Promise<T[]> {\n let qb = this.queryable(table).select(column);\n if (criteria) {\n qb = qb.where(criteria);\n }\n const rows = await qb;\n return rows.map((row: Record<string, unknown>) => row[column] as T);\n }\n\n async grabEntriesFromDatabase(\n table: string,\n criteria?: WhereCriteria,\n ): Promise<RecordData[]> {\n let qb = this.queryable(table).select('*');\n if (criteria) {\n qb = qb.where(criteria);\n }\n return await qb;\n }\n\n async grabNumRecords(table: string, criteria?: WhereCriteria): Promise<number> {\n let qb = this.queryable(table).count({ count: '*' });\n if (criteria) {\n qb = qb.where(criteria);\n }\n const result = await qb.first();\n return Number(result?.count ?? 0);\n }\n\n // ── Manipulation ──\n\n async haveInDatabase(table: string, data: RecordData): Promise<unknown> {\n if (this.client === 'pg' || this.client === 'better-sqlite3' || this.client === 'sqlite3') {\n const [row] = await this.queryable(table).insert(data).returning(this.primaryKeyColumn);\n const pk = typeof row === 'object' && row !== null ? row[this.primaryKeyColumn] : row;\n this.tracker.track(table, this.primaryKeyColumn, [pk]);\n return pk;\n }\n\n // MySQL/MariaDB: no RETURNING clause — use insertId\n const [insertId] = await this.queryable(table).insert(data);\n this.tracker.track(table, this.primaryKeyColumn, [insertId]);\n return insertId;\n }\n\n async updateInDatabase(\n table: string,\n data: RecordData,\n criteria: WhereCriteria,\n ): Promise<number> {\n return await this.queryable(table).where(criteria).update(data);\n }\n\n async deleteFromDatabase(table: string, criteria: WhereCriteria): Promise<number> {\n return await this.queryable(table).where(criteria).delete();\n }\n\n // ── Raw Query ──\n\n async query<T = RecordData[]>(sql: string, bindings?: readonly unknown[]): Promise<T> {\n const result = await this.queryable.raw(sql, bindings as unknown[]);\n\n // Normalize result across drivers:\n // - pg returns { rows: [...] }\n // - mysql2 returns [[rows], fields]\n // - sqlite returns rows directly\n if (Array.isArray(result)) {\n // mysql2: result is [rows, fields]\n if (result.length === 2 && Array.isArray(result[0])) {\n return result[0] as T;\n }\n return result as T;\n }\n if (result && typeof result === 'object' && 'rows' in result) {\n return result.rows as T;\n }\n return result as T;\n }\n\n // ── Transactions ──\n\n async transaction<T>(callback: (trx: DatabaseConnector) => Promise<T>): Promise<T> {\n return await this.queryable.transaction(async (trx) => {\n const nested = new DatabaseConnectorImpl(\n trx,\n new CleanupTracker(),\n this.client,\n this.primaryKeyColumn,\n );\n return callback(nested);\n });\n }\n\n // ── Seeding ──\n\n async loadSqlFile(filePath: string): Promise<void> {\n await SqlLoader.executeFile(this.queryable as Knex, filePath);\n }\n\n async importCsvFile(filePath: string): Promise<void> {\n await CsvLoader.importFile(this.queryable as Knex, filePath);\n }\n\n async assertCsvDataSet(filePath: string): Promise<void> {\n await CsvLoader.assertFile(this.queryable as Knex, filePath);\n }\n}\n","import { createRequire } from 'node:module';\nimport { DatabaseDriverNotFoundError } from './errors.js';\nimport type { DatabaseClient } from './types.js';\n\n/**\n * Validates that the required database driver package is installed.\n * Throws DatabaseDriverNotFoundError with install instructions if not found.\n */\nexport function detectDriver(client: DatabaseClient): void {\n try {\n const _require = createRequire(\n typeof __filename !== 'undefined' ? __filename : import.meta.url,\n );\n _require.resolve(client);\n } catch {\n throw new DatabaseDriverNotFoundError(client);\n }\n}\n","import { expect as baseExpect } from '@playwright/test';\nimport type { DatabaseConnector, WhereCriteria } from './types.js';\n\nexport const expect = baseExpect.extend({\n async toHaveRecord(db: DatabaseConnector, table: string, criteria: WhereCriteria) {\n const assertionName = 'toHaveRecord';\n let count: number;\n\n try {\n count = await db.grabNumRecords(table, criteria);\n } catch (error) {\n return {\n pass: false,\n message: () => `${assertionName}: Failed to query database: ${(error as Error).message}`,\n name: assertionName,\n };\n }\n\n const pass = count > 0;\n\n return {\n pass,\n message: () => {\n if (pass) {\n return `Expected no records in \"${table}\" matching ${JSON.stringify(criteria)}, but found ${count}`;\n }\n return `Expected at least one record in \"${table}\" matching ${JSON.stringify(criteria)}, but found none`;\n },\n name: assertionName,\n expected: pass ? 0 : '>= 1',\n actual: count,\n };\n },\n\n async toHaveRecordCount(\n db: DatabaseConnector,\n expectedCount: number,\n table: string,\n criteria?: WhereCriteria,\n ) {\n const assertionName = 'toHaveRecordCount';\n let actualCount: number;\n\n try {\n actualCount = await db.grabNumRecords(table, criteria);\n } catch (error) {\n return {\n pass: false,\n message: () => `${assertionName}: Failed to query database: ${(error as Error).message}`,\n name: assertionName,\n };\n }\n\n const pass = actualCount === expectedCount;\n const criteriaStr = criteria ? ` matching ${JSON.stringify(criteria)}` : '';\n\n return {\n pass,\n message: () => {\n if (pass) {\n return `Expected record count in \"${table}\"${criteriaStr} to NOT be ${expectedCount}, but it was`;\n }\n return `Expected ${expectedCount} records in \"${table}\"${criteriaStr}, but found ${actualCount}`;\n },\n name: assertionName,\n expected: expectedCount,\n actual: actualCount,\n };\n },\n});\n"],"mappings":"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","names":["fs","path"]}
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@ochorocho/playwright-db-connector",
3
+ "version": "0.0.1",
4
+ "description": "Playwright fixtures for database connectivity with PostgreSQL, MariaDB, MySQL, and SQLite",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/ochorocho/playwright-db-connector.git"
8
+ },
9
+ "publishConfig": {
10
+ "registry": "https://npm.pkg.github.com"
11
+ },
12
+ "type": "module",
13
+ "main": "./dist/index.cjs",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "require": {
23
+ "types": "./dist/index.d.cts",
24
+ "default": "./dist/index.cjs"
25
+ }
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "dev": "tsup --watch",
34
+ "lint": "eslint src tests",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest run",
37
+ "test:unit": "vitest run tests/unit",
38
+ "test:integration": "vitest run tests/integration",
39
+ "test:e2e": "playwright test -c tests/e2e/playwright.config.ts",
40
+ "prepublishOnly": "npm run build"
41
+ },
42
+ "peerDependencies": {
43
+ "@playwright/test": ">=1.40.0",
44
+ "pg": ">=8.0.0",
45
+ "mysql2": ">=3.0.0",
46
+ "better-sqlite3": ">=9.0.0",
47
+ "sqlite3": ">=5.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "pg": {
51
+ "optional": true
52
+ },
53
+ "mysql2": {
54
+ "optional": true
55
+ },
56
+ "better-sqlite3": {
57
+ "optional": true
58
+ },
59
+ "sqlite3": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "knex": "^3.1.0"
65
+ },
66
+ "devDependencies": {
67
+ "@playwright/test": "^1.50.0",
68
+ "@types/better-sqlite3": "^7.6.12",
69
+ "better-sqlite3": "^11.0.0",
70
+ "eslint": "^9.0.0",
71
+ "mysql2": "^3.11.0",
72
+ "pg": "^8.13.0",
73
+ "prettier": "^3.3.0",
74
+ "tsup": "^8.2.0",
75
+ "typescript": "^5.5.0",
76
+ "vitest": "^2.0.0"
77
+ },
78
+ "keywords": [
79
+ "playwright",
80
+ "database",
81
+ "testing",
82
+ "fixtures",
83
+ "postgresql",
84
+ "mysql",
85
+ "mariadb",
86
+ "sqlite",
87
+ "knex",
88
+ "e2e"
89
+ ],
90
+ "license": "MIT"
91
+ }