npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

supawright

v0.3.0

Published

Supawright is a Playwright test harness for E2E testing with Supabase.

Downloads

165

Readme

Supawright

Supawright is a Playwright test harness for E2E testing with Supabase.

Supawright can create database tables and records for you, and will clean up after itself when the test exits. It will create records recursively based on foreign key constraints, and will automatically discover any related records that were not created by Supawright and delete them as well.

Installation

pnpm i -D supawright

Usage

Setup

Unfortunately, Supabase's generated TypeScript types generate an interface, whereas for type constraints, we need a type. So, first change the following line in your generated Supabase types (typically database.ts):

Note: this has now been changed, but will still apply to old Supabase versions.

- export interface Database {
+ export type Database = {

I recommend setting up a make target (or whichever build tool you use) to automatically make this change for you, e.g.

types:
    pnpm supabase gen types typescript --local | \
    sed 's/export interface Database {/export type Database = {/' \
    > src/types/database.ts

Then, create a test file, e.g. can-login.test.ts, and create a test function with the withSupawright function:

import { withSupawright } from 'supawright'
import type { Database } from './database'

const test = withSupawright<
  Database,
  'public' | 'other' // Note 1
>(['public', 'other'])

1: Unfortunately, I haven't found a nice way of infering the schema names from the first argument, so you'll have to specify the schemas you'd like Supawright to use in two places.

Tests

Assuming you have a test function as above, you can now write tests and use the supawright fixture to recursively create database tables. Consider the following table structure:

create table public."user" (
    id uuid primary key default uuid_generate_v4(),
    email text not null unique,
    password text not null
);

create table public.session (
    id uuid primary key default uuid_generate_v4(),
    user_id uuid not null references public."user"(id),
    token text,
    created_at timestamp with time zone not null default now()
);

If you use Supawright to create a session, it will automatically create a user for you, and you can access the user's id in the session's user_id column. Supawright will also automatically generate fake data for any columns that are not nullable and do not have a default value.

test('can login', async ({ supawright }) => {
  const session = await supawright.create('public', 'session')
  expect(session.user_id).toBeDefined()
})

You can optionally pass a data object as the second argument to the create function to override the fake data that is generated. If you pass in data for a foreign key column, Supawright will not create a record for that table.

If your table is in the public schema, you can omit the schema name:

test('can login', async ({ supawright }) => {
  const user = await supawright.create('user', {
    email: '[email protected]'
  })
  const session = await supawright.create('session', {
    user_id: user.id
  })
  // Supawright will not create a user record, since we've passed in
  // a user_id.
  const { data: users } = await supawright.supabase().from('user').select()
  expect(users.length).toBe(1)
})

When the test exits, Supawright will automatically clean up all the records it has created, and will inspect foreign key constraints to delete records in the correct order.

It will also discover any additional records in the database that were not created by Supawright, and will delete them as well, provided they have a foreign key relationship with a record that was created by Supawright.

This runs recursively. Consider the following example:

test('can login', async ({ supawright }) => {
  const user = await supawright.create('user')

  // Since we're using the standard Supabase client here, Supawright
  // is unaware of the records we're creating.
  await supawright
    .supabase()
    .from('session')
    .insert([{ user_id: user.id }, { user_id: user.id }])

  // However, Supawright will discover these records and delete
  // them when the test exits.
})

Note: the .supabase() method of the Supawright object takes an optional schema name to create a Supabase client in the chosen schema.

Overrides

If you have custom functions you wish to use to generate fake data or create records, you can pass optional config as the second argument to the withSupawright function.

The generators object is a record of Postgres types to functions that return a value of that type. Supawright will use these functions to generate fake data for any columns that are not nullable and do not have a default value.

If you're using user defined types, specify the USER-DEFINED type name in the generators object. This will be used for enums, for example.

The overrides object is a record of schema names to a record of table names to functions that return a record of column names to values. Supawright will use these functions to create records in the database. These return an array of Fixtures which Supawright will use to record the records it has created.

This is useful if you use a database trigger to populate certain tables and need to run custom code to activate the trigger.

const test = withSupawright<
    Database,
    'public' | 'other',
>(
    ['public', 'other'],
    {
        generators: {
            smallint: () => 123,
            text: (table: string, column: string) => `${table}.${column}`,
        },
        overrides: {
            public: {
                user: async ({ supawright, data, supabase, generators }) => {
                    const { data: user } = await supabase
                        .from('user')
                        .insert(...)
                        .select()
                        .single()
                    ...
                    return [{
                        schema: 'public',
                        table: 'user',
                        data: user,
                    }]
                }
            }
        }
    }
)

If your generator returns null or undefined, Supawright will fall back to using the built-in generators. In the case of enums, Supawright will pick a random valid enum value.

Teardown

If you'd like to run some code after each test, but before Supawright cleans up after itself, you can pass a beforeTeardown function to the withSupawright function. This function will be passed the Supawright object from the test, as well as the page object from Playwright.

const test = withSupawright<Database, 'public' | 'other'>(['public', 'other'], {
  beforeTeardown: async ({ page, supawright }) => {
    await page.waitForLoadState('networkidle')
  }
})

Connection details

By default, Supawright will look for the SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY environment variables to connect to your Supabase instance. You can override these using the supabase key in the config object.

Supawright also needs access to a Supabase database for schema inspection, and will use the default Supabase localhost database. If you'd like to override this, provide a database key in the config object.

const test = withSupawright<Database, 'public' | 'other'>(['public', 'other'], {
  supabase: {
    supabaseUrl: 'my-supabase-url.com',
    serviceRoleKey: 'my-service-role-key'
  },
  database: {
    host: 'localhost',
    port: 54322,
    user: 'me',
    password: 'password',
    database: 'my-database'
  }
})

Support

Are you a company using Supawright? Get in touch if you're interested in supporting the package, or if you'd like premium support.

TODO

  • [x] Automatically infer allowed enum values from database
  • [ ] Automatically infer custom composite types from database
  • [ ] Fix up my janky typings
  • [x] Come up with a way of using the Database type without having to modify the generated Supabase types
    • This may involve convincing Supabase to change up their generated types