abhinaba-ghosh / playwright-lighthouse

:performing_arts:: Playwright Lighthouse Audit
https://www.npmjs.com/package/playwright-lighthouse
MIT License
245 stars 28 forks source link

Can't run audit on authenticated pages #109

Open alionatudan opened 2 months ago

alionatudan commented 2 months ago

Maybe I am missing something, but I tried two approaches and none works: audit opens the session which is not authenticated even when logs show cookies passed into the page object passed into audit. So, approach 1: Logging in the test

import { test, expect } from "@playwright/test";
import { playAudit } from "playwright-lighthouse";

test("Run lighthouse audit", async () => {
  const url = process.env.STAGING_URL ?? "";
  const user = process.env.USER_NAME ?? "";
  const password = process.env.PASSWORD ?? "";
  const playwright = await import("playwright");
  const browser = await playwright["chromium"].launch({
    args: ["--remote-debugging-port=9222"],
  });
  // const context = await browser.newContext();
  const page = await browser.newPage();

  await test.step("Log in ", async () => {
    console.log(`Authenticating ON STAGING ${url} with user: ${user}`);
    await page.goto(url);
    await page.getByPlaceholder("Username").fill(user);
    await page.getByPlaceholder("Password").fill(password);
    await page.getByRole("button", { name: "Log In" }).click();
    await page.waitForLoadState("load");
    await expect(
      page.getByRole("heading", { name: "Welcome" }).isVisible()
    ).toBeTruthy();
    const cookies = await page.context().cookies();
    console.log("Cookies when logged : ", cookies);
  });

  await test.step("Run lighthouse audit", async () => {
    const cookies = await page.context().cookies();
    console.log("Cookies before audit : ", cookies);
    await playAudit({
      page: page,
      port: 9222,
      thresholds: {
        performance: 0,
        accessibility: 0,
        "best-practices": 0,
        seo: 0,
        pwa: 0,
      },
      reports: {
        formats: {
          html: true,
          json: false,
          csv: false,
        },
        directory: "litghthouse-reports-" + Date(),
      },
    });
  });

  await browser.close();
});

Cookies are logged in both steps, but audit is ran on the non-authorized page.

Approach 2: setting up configurations to auth before running test

Config:

mport { chromium, test as base } from "@playwright/test";
import type { BrowserContext, Page } from "@playwright/test";
import getPort from "get-port";
import os from "os";
import path from "path";
import authState from "./.auth/user.json";
import fs from "fs";

type LocalStorage = Array<{ name: string; value: string }>;

type AuthState = {
  origins: Array<{
    origin: string;
    localStorage: Array<{
      name: string;
      value: string;
    }>;
  }>;
};
export const lighthouseTest = test.extend<
  {
    lighthouseAuthenticatedPage: Page;
    lighthousePage: Page;
    context: BrowserContext;
  },
  {
    port: number;
    storage: LocalStorage;
  }
>({
  port: [
    async ({}, use) => {
      // Assign a unique port for each playwright worker to support parallel tests
      const port = await getPort();
      await use(port);
    },
    { scope: "worker" },
  ],
 // As there's currently no way to insert auth-state into a PersistentContext we
  // need to explicity load and insert the auth storage. In our case we only care
  // about localStorage for auth.
  storage: [
    async ({}, use) => {
      const rootUrl = process.env.STAGING_URL || "";
      const localhostStorage = (authState as AuthState).origins.find(
        ({ origin }) => {
          return origin === new URL(rootUrl).origin;
        }
      );
      if (!localhostStorage) {
        throw new Error(`No storage state found in ${authState}`);
      }
      if (!localhostStorage.localStorage.length) {
        throw new Error(`No localStorage state found in ${authState}`);
      }
      await use(localhostStorage.localStorage);
    },
    { scope: "worker" },
  ],

  context: [
    async ({ port, launchOptions }, use) => {
      const userDataDir = path.join(os.tmpdir(), "pw", String(Math.random()));
      const cookies = require(path.resolve(
        __dirname,
        "./.auth/user.json"
      )).cookies;

      // Convert cookies to the correct format for the header
      const cookieHeader = cookies
        .map((cookie) => `${cookie.name}=${cookie.value}`)
        .join("; ");

      const context = await chromium.launchPersistentContext(userDataDir, {
        args: [
          ...(launchOptions.args || []),
          `--remote-debugging-port=${port}`,
        ],
        extraHTTPHeaders: {
          cookie: cookieHeader,
        },
      });
     // apply state previously saved in the the `globalSetup`
      await context.addCookies(require("./.auth/user.json").cookies);
      await context.storageState({ path: "./.auth/user.json" });
      await use(context);
      await context.close();
    },
    { scope: "test" },
  ],

    lighthousePage: [
      async ({ context: context }, use) => {
         const page = await context.newPage();
         await use(page);
       },
       { scope: "test" },
     ],

     lighthouseAuthenticatedPage: [
       async ({ context: context, lighthousePage: page, storage }, use) => {
         const hostname = new URL(process.env.STAGING_URL || "").hostname;
         await page.goto("dashboard");
         await insertAuthStorage(page);
         type AuthState = {
         cookies: Array<{
             name: string;
             value: string;
             domain: string;
             path: string;
            expires: number;
             httpOnly: boolean;
            secure: boolean;
           sameSite: "Strict" | "Lax" | "None";
           }>;
           origins: Array<{
             origin: string;
            localStorage: Array<{
               name: string;
              value: string;
             }>;
           }>;
        };
 //       async function insertAuthStorage(page: Page): Promise<void> {
  //         const authState: AuthState = JSON.parse(
  //           fs.readFileSync(
  //             // getAuthArtifactFilePath("state", "storageState.json"),
  //             path.resolve(__dirname, "./.auth/user.json"),
  //             "utf8"
  //           )
  //         );
  //         // Set cookies
  //         for (let cookie of authState.cookies) {
  //           if (cookie.domain === hostname) {
  //             const cookieValue = `${cookie.name}=${
  //               cookie.value
  //             };expires=${new Date(cookie.expires * 1000).toUTCString()};path=${
  //               cookie.path
  //             };domain=${cookie.domain};secure=${cookie.secure};httponly=${
  //               cookie.httpOnly
  //             };samesite=${cookie.sameSite}`;

  //             await page.evaluate(
  //               (cookieValue) => (document.cookie = cookieValue),
  //               cookieValue
  //             );
  //           }
  //         }

  //         // Set local storage
  //         const origin = "https://" + hostname;
  //         console.log("ORIGIN: ", origin);
  //         const localStorageKVPs = authState.origins.find((o) => {
  //           return o.origin === origin;
  //         });
  //         if (localStorageKVPs) {
  //           for (let kvp of localStorageKVPs.localStorage) {
  //             await page.evaluate(
  //               (kvp) => localStorage.setItem(kvp[0], kvp[1]),
  //               [kvp.name, kvp.value]
  //             );
  //           }
  //         }
  //       }
  //       await use(page);
  //     },
  //     { scope: "test" },
  //   ],
});
export async function lighthouseAudit(
  page: Page,
  port: number,
  pageName: string
  // thresholds?: LighthouseThresholds,
  // config = lighthouseMobileConfig
): Promise<void> {
  const { playAudit } = await import("playwright-lighthouse");
  const fileName = `performance-scan-results-${pageName}-${new Date().getDate()}`;
  await playAudit({
    page,
    // config,
    port,
    reports: {
      formats: {
        html: true,
        json: true,
        csv: false,
      },
      name: fileName,
      directory: "./lighthouse",
    },
    thresholds: {
      performance: 0, //70,
      accessibility: 0, // 95,
      pwa: 0, // 50,
      "best-practices": 0, // 95,
      // ...thresholds,
    },
    opts: {
      logLevel: "error",
    },
  });
}

Commented code was also tested and did not influence anything. Test:

import { lighthouseTest, lighthouseAudit, expect } from "../../../my-test";

lighthouseTest.describe("Authenticated route after globalSetup", () => {
  lighthouseTest.use({
    storageState: ".auth/user.json", // Load the authenticated state
  });
  lighthouseTest(
    "should pass lighthouse tests",
    async ({ port, dashboardPage, page }, testInfo) => {
      const pageName = "dashboard";
      const fileName = `performance-scan-results-${pageName}-${new Date().getDate()}`;
      await dashboardPage.goto();
      expect(await dashboardPage.header.isVisible()).toBeTruthy();
      await lighthouseAudit(dashboardPage.page, port, pageName);
      await testInfo.attach(`performance-scan-results-${pageName}`, {
        body: fileName,
        contentType: "csv",
      });
    }
  );
});
alionatudan commented 2 months ago

Used info from here: https://github.com/abhinaba-ghosh/playwright-lighthouse/issues/14 And here: https://github.com/microsoft/playwright/issues/7447#issuecomment-879803423

ritesh-csg commented 3 weeks ago

Hi @alionatudan

Did you found a way to solve the above issue? Even i have the same issue, for me when i'm passing the page object to playaudit, it is opening a new window which doesn't have auth info so while accessing the it is again redirected to auth service which shouldn't be the case as testcases are working fine.

alionatudan commented 3 weeks ago

Hi @alionatudan

Did you found a way to solve the above issue? Even i have the same issue, for me when i'm passing the page object to playaudit, it is opening a new window which doesn't have auth info so while accessing the it is again redirected to auth service which shouldn't be the case as testcases are working fine.

No, I could not resolve this. Switched to Artillery.

Arghajit47 commented 2 weeks ago

Even I have found the same issue when tried to login via cookies. Kindly mention if any solution comes up