DoesQA vs Playwright: the same test compared

DoesQA vs Playwright: the same test compared

Want to see the same test in DoesQA and Playwright?

Cool. Pick a journey real humans do: create an account with email verification, or reset a forgotten password with email. Same door in. Same "you're in" screen out. Two branches that start together and finish together.

In DoesQA this can be one Flow. Shared Open. Shared first clicks. Split. Join. Email inbox included.

In Playwright that is... well. Selectors. Page objects. A mail helper you wired up at 11pm. A remote runner you do not fully control. And a second copy of half of that when the forgotten password path "is basically the same" until it isn't.

If you want hundreds of lines of code, a huge amount of duplication, and flaky runs on third-party remote runners, the choice is clear.

If you want solid, easy-to-maintain quality assurance, check out DoesQA.

The journey

One product surface. Two paths:

  1. Register with email verification

  2. Forgotten password with email reset

Both open the app, hit Register, confirm the Login / Register screen, then split. Both end on Welcome. In DoesQA that shape is a later split: shared start, two branches, one join.



Auth Flow: register or forgotten password

That canvas is the whole point. Shared start. Branch labels. Mail steps on both sides. One Welcome check at the bottom. Two Test Cases from one Flow.

How-to detail lives in the docs: registration and email verification and forgotten password.

The same test in DoesQA

Rough map of the Flow above:

Stage

What happens

Shared start

Open https://example.com on Chrome. Touch Register. Check Login / Register is displayed.

Register branch

Set Email to testEmail. Set Password to NEW_PASSWORD. Touch Submit. Wait for new Test Case email. Open latest. Touch Verify email link.

Forgotten Password branch

Touch Already have an account. Set Email to testEmail. Touch Submit. Wait for new Test Case email. Open latest. Touch Reset password link. Set Password to NEW_PASSWORD. Touch Save new password.

Shared end

Check Welcome heading text contains Welcome.

No custom mail server in the test. No second framework for inbox. The Test Case inbox is a first-class step. Selectors live in shared Elements. Change Email once; both branches pick it up.

That is what "same start, same end" looks like when the product was built for journeys, not for assembling a lab around a browser API.

The same test in Playwright

Here is a fair, slightly-better-than-average sketch of that same Flow. Page objects. A small mail helper. Two specs that share the start and still duplicate the glue.

// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// auth.pages.ts

import
 
{
 
Page
,
 
expect
 
}
 
from
 
'@playwright/test'
;

export
 
class
 AuthPages 
{

  
constructor
(
private
 readonly page
:
 Page
)
 
{
}

  
async
 
open
(
)
 
{

    
await
 
this
.
page
.
goto
(
'https://example.com'
)
;

  
}

  
async
 
touchRegister
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Register'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectLoginOrRegister
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByText
(
'Login / Register'
)
)
.
toBeVisible
(
)
;

  
}

  
async
 
touchAlreadyHaveAnAccount
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'link'
,
 
{
 
name
:
 
'Already have an account'
 
}
)
.
click
(
)
;

  
}

  
async
 
fillEmail
(
email
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Email'
)
.
fill
(
email
)
;

  
}

  
async
 
fillPassword
(
password
:
 string
)
 
{

    
await
 
this
.
page
.
getByLabel
(
'Password'
)
.
fill
(
password
)
;

  
}

  
async
 
submit
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Submit'
 
}
)
.
click
(
)
;

  
}

  
async
 
saveNewPassword
(
)
 
{

    
await
 
this
.
page
.
getByRole
(
'button'
,
 
{
 
name
:
 
'Save new password'
 
}
)
.
click
(
)
;

  
}

  
async
 
expectWelcome
(
)
 
{

    
await
 
expect
(
this
.
page
.
getByRole
(
'heading'
,
 
{
 
name
:
 
/Welcome/i
 
}
)
)
.
toBeVisible
(
)
;

  
}

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// mail.ts

// "Slightly better than average" still means you own this.

export
 
async
 
function
 
waitForNewMessage
(
inboxId
:
 string
)
 
{

  
// poll your mail API / Mailosaur / custom inbox service

}

export
 
async
 
function
 
openLatestAndClick
(
linkText
:
 RegExp
)
 
{

  
// fetch HTML, find anchor, return href for page.goto

}
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// register.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'register with email verification'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
`user+
${
Date
.
now
(
)
}
@mail.test`
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
verifyUrl
 = 
await
 
openLatestAndClick
(
/verify|confirm/i
)
;

  
await
 
page
.
goto
(
verifyUrl
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;
// forgotten-password.spec.ts

import
 
{
 
test
 
}
 
from
 
'@playwright/test'
;

import
 
{
 
AuthPages
 
}
 
from
 
'./auth.pages'
;

import
 
{
 
waitForNewMessage
,
 
openLatestAndClick
 
}
 
from
 
'./mail'
;

test
(
'forgotten password with email'
,
 
async
 
(
{
 
page
 
}
)
 
=>
 
{

  
const
 
auth
 = 
new
 
AuthPages
(
page
)
;

  
const
 
email
 = 
process
.
env
.
EXISTING_USER_EMAIL
!
;

  
const
 
password
 = 
process
.
env
.
NEW_PASSWORD
!
;

  
await
 
auth
.
open
(
)
;

  
await
 
auth
.
touchRegister
(
)
;

  
await
 
auth
.
expectLoginOrRegister
(
)
;

  
await
 
auth
.
touchAlreadyHaveAnAccount
(
)
;

  
await
 
auth
.
fillEmail
(
email
)
;

  
await
 
auth
.
submit
(
)
;

  
await
 
waitForNewMessage
(
email
)
;

  
const
 
resetUrl
 = 
await
 
openLatestAndClick
(
/reset|password/i
)
;

  
await
 
page
.
goto
(
resetUrl
)
;

  
await
 
auth
.
fillPassword
(
password
)
;

  
await
 
auth
.
saveNewPassword
(
)
;

  
await
 
auth
.
expectWelcome
(
)
;

}
)
;

That is the polite version of the canvas you just looked at. You still have not counted CI config, parallel workers, the browser cloud bill, reporting, flake retries, and the meeting where someone asks why register is green while reset is "flaky again".

How long does this take?

Rough estimate, from scratch, for a very good Playwright developer who already knows the tooling: a day to a day and a half.

That is project setup, page objects, standing up a mail provider, wiring the helper, both paths green once, and the usual "why is the inbox empty" diversion. Not CI. Not a runner farm. Not the second week of flakes.

The same Flow in DoesQA, from a newly created blank account: about 10 minutes.

Open. Touch. Check. Branch. Mail steps. Welcome. Done. Hosted runners and Results are already there.

Playwright is good at driving a single browser. It is not a mail product, a runner product, a results product, or a maintenance product. Those arrive as extra code and extra vendors. See The Real Cost of Playwright and DoesQA compared.

Where DoesQA is better (and we're not shy about it)

Same journey. Look at the maintenance story.

Change

DoesQA

Playwright sketch above

Email field selector moves

Update one Element

Hunt locators across pages and specs

Verify link copy changes

Update one Element

Touch mail helper and both specs

Shared start changes (Open / Register / Login screen)

Edit once on the Flow

Edit both specs and hope the page object still matches

Add a third auth path

Another branch on the same Flow

Another spec file, more shared-but-not-shared glue

Debug a fail

Step timeline, screenshot, video on the Run

Logs, your reporter, the runner vendor's UI

DoesQA ships authoring, shared Steps, hosted runners, and Results as one platform. When something is wrong, it is DoesQA's problem to fix the platform. That is the difference between a Flow and a second development repo that needs to be maintained.

For more on the benefits of Codeless versus code: Codeless vs coded.

So... which one is the same test?

Both prove register and forgotten password.

Only one keeps the shared start and shared end as a single maintained object without creating a second engineering team.

If you want hundreds of lines of code, a huge amount of duplication, and flaky runs on third-party remote runners, Playwright is free. Help yourself.

If you want solid, easy-to-maintain quality assurance, start a DoesQA trial.

DoesQA Flows dashboard

Get your next release tested end to end.

Hosted journeys with email, MFA, and branching. Shared Elements keep maintenance low. Pass and fail from real hosted browsers.

Get your next release tested end to end.

Hosted journeys with email, MFA, and branching. Shared Elements keep maintenance low. Pass and fail from real hosted browsers.

DoesQA Flows dashboard

Get your next release tested end to end.

Hosted journeys with email, MFA, and branching. Shared Elements keep maintenance low. Pass and fail from real hosted browsers.