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.09k stars 3.61k forks source link

[Question] signing using google account #18866

Open kedevked opened 1 year ago

kedevked commented 1 year ago

I am trying to sign in to a website that uses a google authentication. I have been able to do the sign in while using cypress but I have not been able to achieve the same behavior using playwright. Please find below the two specs

it('should login', () => {
cy.request({
    method: 'POST',
    url: 'https://www.googleapis.com/oauth2/v4/token',
    body: {
      grant_type: 'refresh_token',
      client_id: 'client_id',
      client_secret: 'client_secret',
      refresh_token: 'refresh_token',
    },
  }).then(({ body }) => {
    const { access_token, id_token } = body;

    cy.request({
      method: 'GET',
      url: 'https://www.googleapis.com/oauth2/v3/userinfo',
      headers: { Authorization: `Bearer ${access_token}` },
    }).then(({ body }) => {
      cy.log(body);
      const userItem = {
        token: id_token,
        user: {
          googleId: body.sub,
          email: body.email,
          givenName: body.given_name,
          familyName: body.family_name,
          imageUrl: body.picture,
        },
      };

      cy.visit(settings.website);
      cy.contains('is logged in')
})

With playwrite, I wrote the following spec:

test("should log user in", async ({ page }) => {

  // const requestContext = await page.request.newContext();
  const response = await page.request.post(
    "https://www.googleapis.com/oauth2/v4/token",
    {
      data: {
        grant_type: "grant_type",
        client_id:
          "client_id",
        client_secret: "client_secret",
        refresh_token:
          "refresh_token",
      },
    }
  );
  const content = await response.json();
  const { access_token, id_token } = content;

  const rawResponse2 = await page.request.get(
    "https://www.googleapis.com/oauth2/v3/userinfo",
    {
      headers: { Authorization: `Bearer ${access_token}` },
    }
  );

  const data = (await rawResponse2.json()) as any;

  const userItem = {
    token: id_token,
    user: {
      googleId: data.sub,
      email: data.email,
      givenName: data.given_name,
      familyName: data.family_name,
      imageUrl: data.picture,
    },
  };
  console.log("userItem", userItem); // userItem is well displayed in the console

  await page.goto(settings.website);

  await expect(page).toContain("is logged in"); // test fails, the user is still not logged in
});

An issue has already been created and closed here. But after discussing with @yury-s he asked me to recreate it adding how to reproduce the behavior. Here is a repo where I showcase of a small angular app needing to signin to a google account. I have added all the instructions to configure firebase and get the google credentials. But I can provide my own inbox if necessary as well https://github.com/kedevked/todos

Depechie commented 1 year ago

Would be nice if we can get a similar example for Azure AD... that would be https://login.microsoftonline.com//oauth2/v2.0/token

djamaile commented 1 year ago

Any workaround?

anirbandan commented 1 year ago

Any progress on this issue? do you know if this implementation would work the google account has 2FA activated?

slamdev commented 1 year ago

Would be nice if we can get a similar example for Azure AD... that would be https://login.microsoftonline.com//oauth2/v2.0/token

adopted from https://github.com/opendevstack/ods-quickstarters/blob/master/e2e-cypress/files/support/msalv2-login.ts

global-setup.ts:

/// <reference types="node" />

import {FullConfig} from '@playwright/test';
import * as fs from "fs";

const fetch = require("node-fetch")

async function globalSetup(config: FullConfig) {
    const {baseURL, storageState} = config.projects[0].use;
    const cacheEntries = await msalStorageState()
    const storage = Object.entries(cacheEntries).map(([k, v]) => ({'name': k, 'value': JSON.stringify(v)}));
    const customStorageState = {
        origins: [{
            origin: baseURL as string,
            localStorage: storage,
        }]
    }
    fs.writeFileSync(storageState as string, JSON.stringify(customStorageState))
}

async function msalStorageState() {
    const aadTenantId = process.env.TENANT_ID || ''
    const aadClientId = process.env.CLIENT_ID || ''
    const aadClientSecret = process.env.CLIENT_SECRET || ''
    const aadUsername = process.env.USERNAME || ''
    const aadPassword = process.env.PASSWORD || ''

    const scopes = [
        'openid',
        'profile',
        'user.read',
        'email',
        'offline_access', // needed to get a refresh token
    ];

    const tenant = `https://login.microsoftonline.com/${aadTenantId}`;
    const tenantUrl = `${tenant}/oauth2/v2.0/token`;
    const params = new URLSearchParams();
    params.append('client_info', '1');
    params.append('grant_type', 'password');
    params.append('scope', scopes.join(' '));
    params.append('client_id', aadClientId);
    params.append('client_secret', aadClientSecret);
    params.append('username', aadUsername);
    params.append('password', aadPassword);

    const res = await fetch(tenantUrl, {
        body: params,
        method: "POST",
    })
    const tokens = await res.json();

    const cachedAt = Math.round(new Date().getTime() / 1000);
    const expiresOn = cachedAt + tokens.expires_in;
    const extendedExpiresOn = cachedAt + tokens.ext_expires_in;

    const id_token = tokens.id_token ? JSON.parse(Buffer.from(tokens.id_token.split('.')[1], 'base64').toString('utf-8')) : null;

    const clientId = id_token?.aud;
    const tenantId = id_token?.tid;
    const userId = id_token?.oid;
    const name = id_token?.name;
    const username = id_token?.preferred_username;

    const environment = 'login.windows.net';
    const homeAccountId = `${userId}.${tenantId}`;

    const cacheEntries: { [key: string]: any } = {};

    cacheEntries[`${homeAccountId}-${environment}-${tenantId}`] = {
        authorityType: 'MSSTS',
        clientInfo: tokens.client_info,
        environment,
        homeAccountId,
        localAccountId: userId,
        name,
        realm: tenantId,
        username,
    };

    cacheEntries[`${homeAccountId}-${environment}-accesstoken-${clientId}-${tenantId}-${tokens.scope}`] = {
        cachedAt: cachedAt.toString(),
        clientId,
        credentialType: 'AccessToken',
        environment,
        expiresOn: expiresOn.toString(),
        extendedExpiresOn: extendedExpiresOn.toString(),
        homeAccountId,
        realm: tenantId,
        secret: tokens.access_token,
        target: tokens.scope,
    };

    cacheEntries[`${homeAccountId}-${environment}-idtoken-${clientId}-${tenantId}-`] = {
        clientId,
        credentialType: 'IdToken',
        environment,
        homeAccountId,
        realm: tenantId,
        secret: tokens.id_token,
    };

    cacheEntries[`${homeAccountId}-${environment}-refreshtoken-${clientId}--`] = {
        clientId,
        credentialType: 'RefreshToken',
        environment,
        homeAccountId,
        secret: tokens.refresh_token,
    };

    return cacheEntries
}

export default globalSetup;

playwright.config.ts:

export default defineConfig({
    globalSetup: require.resolve('./global-setup'),
    use: {
        baseURL: process.env.BASE_URL || 'http://localhost:8081',
        storageState: '/tmp/storageState.json',
    },
});
vasylshlapaks commented 1 year ago

+1 for this, it's a real problem if we want to test google login

einarpersson commented 1 year ago

+1 this is very important

sirj77 commented 1 year ago

+1 as it is not good to have it working in Cypress, but not working in Playwright

aminaodzak commented 1 year ago

Is this really still not fixed?

lalitlsg commented 1 year ago

please tell the best way to write test if your app requires google login

Himanshu21git commented 1 year ago

I am facing a similar issue, a solution or workaround will be highly appreciated.

marcin-bojar commented 11 months ago

Signing in to Google account (via UI) on Webkit and Chromium doesn't work. I get "This browser or app may not be secure" screen. It does work for me on Firefox, though.

StevenBoutcher commented 6 months ago

Has any progress been made on this?

kedevked commented 4 months ago

2 years after, any progress on this issue ?

fungairino commented 4 months ago

Has anybody hacked a work-around to authenticate programmatically with google oauth for playwright?

sirj77 commented 4 months ago

@debs-obrien maybe you could clarify what's the status here as many people expect to get the feedback?

aendel commented 2 months ago

+1

matiasluduena23 commented 1 month ago

+1

fungairino commented 3 weeks ago

Does cypress actually do anything special with google auth requests? I noticed in the article where they describe setting this up that it sets the user access token to local storage which it then uses in the example application with a conditional check

Theoretically you could do something similar in playwright, just using the equivalent page.request and page.evaluate methods for making requests and setting localstorage values.

kosteklvp commented 2 weeks ago

Anything new about this?