microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.84k stars 3.67k forks source link

[Question] Set global variable from global-setup #7445

Closed frassinier closed 3 years ago

frassinier commented 3 years ago

Hey!

Can I pass an object from globalSetup fn to all my tests?

Imagine that I would like to use a visual regression testing SaaS tool (Applitools for example),

// global-setup.js
const { chromium } = require("@playwright/test");

const {
  Eyes,
  ClassicRunner,
  Configuration,
  BatchInfo
} = require("@applitools/eyes-playwright");

require("dotenv").config();

module.exports = async config => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  const runner = new ClassicRunner();
  const eyes = new Eyes(runner);
  const configuration = new Configuration();
  configuration.setApiKey(process.env.APPLITOOLS_API_KEY);
  configuration.setBatch(new BatchInfo("Demo batch"));

  // Expose eyes object somewhere here!

  await page.goto(process.env.BASE_URL);
  await page.click("button");
  await page.fill("#username", process.env.USERNAME);
  await page.fill("#password", process.env.PASSWORD);
  await Promise.all([
    page.waitForNavigation(),
    page.click("#login-submit-button")
  ]);
  await page.context().storageState({ path: ".tmp/state.json" });
  await browser.close();

  return async () => {
    await eyes.abortIfNotClosed();
  };
};

And then I would love to be able to write a test like that

test.describe("DemoApp - ClassicRunner", () => {
  test("Smoke Test", async ({ page, eyes /* Imagine I said */ }) => {
    await eyes.open(
      page,
      "DemoApp",
      "Smoke Test",
      new RectangleSize(1368, 768)
    );
    await eyes.check("Login Window", Target.window().fully());
    await eyes.close();
  });
});

Or maybe I'm completely wrong about how to achieve it?

pavelfeldman commented 3 years ago

You want to create a new eyes fixture instead. Some docs here:

// myTest.ts
import { test as baseTest } from '@playwright/test';
export { expect } from '@playwright/test';

export const test = baseTest.extend<{
  eyes: Eyes;
}>({
  eyes: async ({}, use) => {
    const eyes = new Eyes();
    await use(eyes);
    await eyes.close();
  },
});

// test.spec.ts
import { test, expect } from './myTest';

test("use eyes", async ({ eyes, page }) => {
  // Use eyes here.
});
pavelfeldman commented 3 years ago

If you want to reuse the runner among those, you need a worker fixture with the runner. That worker fixture will be created once per worker and will be shared among tests that worker runs. See https://playwright.dev/docs/test-fixtures#worker-fixtures for more details:

// myTest.ts
const test = baseTest.extend<{
  eyes: Eyes;
}, {
  eyesRunner: ClassicRunner
}>({
  eyesRunner: [async ({}, use) => {
    // Set up once per worker.
    await use(new ClassicRunner());
  }, { scope: 'worker'}],
  eyes: async ({ eyesRunner }, use) => {
    const eyes = new Eyes(eyesRunner);
    await use(eyes);
    await eyes.close();
  },
});

// test.spec.ts
import { test, expect } from './myTest';

test("use eyes", async ({ eyes, page }) => {
  // Use eyes here.
});
frassinier commented 3 years ago

Thank you, Pavel! 💯 Exactly what I need! The second snippet works like a charm!

fredboyle commented 2 years ago

@pavelfeldman This information appears to be what I'm seeking for my issue however it is unclear whether the fixture runs this code only once and then passes the value to each test (desired), or if it runs it for every test and adds it to the context of the test. Which is it?

I only need such code to run once before all tests, globalSetup style, but then need the resulting variable to be available within in each test. Fixtures don't seem to be what is needed based on my understanding but I'm likely wrong. Would you please help clarify?

As is mentioned by the original poster I want to run const eyes = new Eyes(runner) only once and have the eyes variable be accessible within each test.

Thanks.

denis-domanskii commented 2 years ago

@frassinier could you please reopen the question due to the last comment?

joeflan commented 2 years ago

I have the exact same problem as @fredboyle. What's the best solution here?

Looked into this more and I think you can just use the method defined here: https://playwright.dev/docs/test-advanced#global-setup-and-teardown

Once your object is defined in the global-setup, you can set it as an environment variable with process.env.FOO. Then in your test you can make it available with const { FOO } = process.env;