testing-library / playwright-testing-library

🔍 Find elements in Playwright with queries from Testing Library
MIT License
248 stars 9 forks source link

✨ Add screen fixture API that combines queries with Page API #498

Closed jrolfs closed 2 years ago

jrolfs commented 2 years ago

Thanks to @gajus for the inspiration here. This adds a screen fixture that combines Playwrights page API with the document-scoped Testing Library queries (queries). This will likely replace queries altogether in the official release of this stuff.

Example

test('screen fixture responds to Page and Query methods', async ({screen}) => {
  const locator = screen.getByRole('button', {name: /getBy.*Test/})
  expect(await locator.textContent()).toEqual('getByRole Test')

  await screen.goto(`file://${path.join(__dirname, '../fixtures/late-page.html')}`)

  const delayedLocator = await screen.findByText('Loaded!', undefined, {timeout: 3000})
  expect(await delayedLocator.textContent()).toEqual('Loaded!')
})
gajus commented 2 years ago

Love this!

Few considerations:

jrolfs commented 2 years ago

@gajus thanks for taking a look! Great points...


How does one create a new screen?, e.g. most of our tests use two browser contexts

Could you expand on this and/or provide a specific example? How exactly are you creating/using the additional contexts? I would hope that most of these use cases are solved for in @playwright/test so that simply configuring it to run with whatever contexts you need will ensure that all you need is the screen fixture that wraps the page that Playwright provides and @playwright/test manages executing the tests in different contexts.

@sebinsua I guess you had a similar use case that prompted you to suggest we expose queriesFor. I'd be curious to hear more about your case as well.

Somewhat related: I am trying to figure out the Locator API I want to use for the "vanilla" playwright use case where fixtures aren't an option. I was thinking of something like const screen = getScreenForPage(page), but it would need to set up the selector engine, read a global configuration, and inject the Testing Library script into the page instance "on demand" since we can't rely on @playwright/test fixtures in that case. Maybe something like this makes sense:


Does chaining work? This was one of the limitations we faced with our Proxy implementation https://github.com/microsoft/playwright/issues/16785

So I had been considering just sticking with within(locator: Locator) here for consistency with Testing Library, but @sebinsua also asked about this and I don't want to adhere to the Testing Library API solely for the sake of consistency. I could see chaining being quite powerful, and probably a bit more of an idiomatic implementation since we're working with Locators. I can also see within()/chaining being much more common in e2e tests when compared to unit tests. I think I'm gonna try implementing something that would even work with the asynchronous find* queries.

// Synchronous
const locator = screen.getByRole('form').within().getByRole('textbox');
// Equivalent
const formLocator = screen.getByRole('form');
const locator = within(formLocator).getByRole('textbox');

// Asynchronous (waits for a delayed `<form />` and then queries within it if found)
const locator = await screen.findByRole('form').within().getByRole('textbox');
// Equivalent
const formLocator = await screen.findByRole('form');
const locator = within(formLocator).getByRole('textbox');

I'll have to prove out some assumptions I've made regarding some cleverness that'd be necessary to handle the async stuff. I'll try to get something hacked together soon, but let me know if you have a different angle in mind.

jrolfs commented 2 years ago

@gajus take a look at #501 for my take on chaining so far.

Additional thought regarding:

How does one create a new screen?, e.g. most of our tests use two browser contexts

We could continue to expose the within() fixture as an alternative to chaining and if you pass it a Page instance, it would return a Screen instance.

gajus commented 2 years ago

Looks good. I would def expose within() though, since it will be needed for anyone who is testing more than 1 page.

CC @alyaothman14 who currently owns Playwright migration on our team, in case she has input here.

jrolfs commented 2 years ago

Would you be able to use the within() provided as a @playwright/test fixture @gajus / @alyaothman14? Just curious because that one is currently wired up to the test.use() configuration whereas the imported function needs to be configured independently.

Either way, I'm going to add the Screen functionality to within() in this PR and get it released on beta for y'all.

github-actions[bot] commented 2 years ago

:tada: This PR is included in version 4.4.0-beta.6 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

jrolfs commented 2 years ago

@gajus / @alyaothman14 released on beta 🥳