Invite a member and join

An owner invites a member by email, and the member joins and sees the project.

This example uses Parcel, a demo store at parcel.example.com. Parcel is a demo project, not a customer.

Two people take part. The owner invites a member by email. The member accepts from the email and sees the Parcel project.

Before you start Link to Before you start

Each person needs a test account. Add both under Re:agent uses, in Test accounts.

NameKindAddress
ownerPasswordowner@parcel.example.com, with its password
memberWith an email code or linkRehearsal makes one, such as parcel-member@inbox.rehearsal.dev

The member needs an account that signs in with an email code or link, because the invitation must reach its test inbox. An account with a password gets an address only when its code after the password is sent by email.

Ask Re:agent Link to Ask Re:agent

Open Re:agent and type: "Check that an invited member can join Parcel and see the project. The owner sends the invitation."

What Re:agent does Link to What Re:agent does

Re:agent opens a browser for each person, as flows with more than one person describes. Each line below is a row in the chat, in the app's own words. What follows a dot is the detail the chat shows beside the row.

  • Checked your projects
  • Listed 2 test accounts

In the owner's browser:

  • Opened a browser on parcel.example.com as owner
  • Opened parcel.example.com/sign-in
  • Filled Email · owner@parcel.example.com
  • Filled Password
  • Clicked Sign in
  • Clicked Members
  • Clicked Invite a member
  • Filled Email · parcel-member@inbox.rehearsal.dev
  • Chose an option in Role · Member
  • Clicked Send invitation
  • Read the page · Invitation sent

In the member's browser:

  • Opened a browser on parcel.example.com as member
  • Read the test inbox · 1 message
  • Opened parcel.example.com/invite
  • Filled Name · Mira Okafor
  • Clicked Accept invitation
  • Read the page · Parcel

The last row is Saved the flow.

Re:agent never sees the owner's password. It types {{password}}, and Rehearsal fills in the real one. The link from the email is written down as {{link}}.

Then a card in the chat follows the new test. It moves through Flow saved, Test written, First run passed and Verified in a fresh browser.

The test Link to The test

Rehearsal writes a Playwright test from the saved flow. It looks like this, with a browser for each person.

TypeScript
import { expect, test } from '@playwright/test';import { participant } from '@rehearsal/codegen/runtime';test('Invited member can join and see the project', async ({ browser }) => {  const owner = await participant(browser, 'owner');  const member = await participant(browser, 'member');  try {    await test.step('Sign in as the owner', async () => {      await owner.page.goto('/sign-in');      await owner.page.getByLabel('Email').fill('owner@parcel.example.com');      await owner.page.getByLabel('Password').fill(owner.password());      await owner.page.getByRole('button', { name: 'Sign in' }).click();      await owner.page.waitForURL('**/projects');    });    await test.step('Invite the member', async () => {      await owner.page.getByRole('link', { name: 'Members' }).click();      await owner.page.getByRole('button', { name: 'Invite a member' }).click();      await owner.page.getByLabel('Email').fill('parcel-member@inbox.rehearsal.dev');      await owner.page.getByLabel('Role').selectOption('Member');      await owner.page.getByRole('button', { name: 'Send invitation' }).click();    });    await test.step('Check the invitation is listed', async () => {      await expect(owner.page.getByText('parcel-member@inbox.rehearsal.dev'))        .toBeVisible();    });    await test.step('Open the invitation as the member', async () => {      const email = await member.latestEmail({ subject: 'invited' });      await member.page.goto(email.links[0]);    });    await test.step('Accept the invitation', async () => {      await member.page.getByLabel('Name').fill('Mira Okafor');      await member.page.getByRole('button', { name: 'Accept invitation' }).click();      await member.page.waitForURL('**/projects');    });    await test.step('Check the member sees the Parcel project', async () => {      await expect(member.page.getByRole('link', { name: 'Parcel' })).toBeVisible();    });  }  finally {    await owner.close();    await member.close();  }});

owner.password() reads the password when the test runs. member.latestEmail() waits for the invitation in the member's test inbox. Neither value is in the file.

The member's address stays the same on every run. If your app rejects a second invitation to it, ask Re:agent to end the flow by removing the member.

Run it Link to Run it

Press Run now on the card in the chat, or Run on the test's row in Tests. From the terminal:

Terminal
rehearsal runs start --test <test-id>rehearsal runs wait <run-id>

The test uses two browsers and still counts as one test run.

Next steps Link to Next steps