首页 > 解决方案 > 在 CI 环境中使用 Playwright 登录 Google

问题描述

我有一个剧作家/摩卡脚本,可以让我登录谷歌。此代码在我的本地计算机上运行时有效,但gitlab-ci.

e2e.spec.js

describe('Staging', async function() {
  before(async function() {
    await playwright.selectors.register(selectorEngine, { name: 'shadow' });
    browser = await playwright.chromium.launch({
      args: [
        '--disable-web-security',
        '--disable-features=IsolateOrigins,site-per-process',
      ],
    });

    context = await browser.newContext({
      geolocation: { latitude: 40.0583, longitude: 74.4057 },
      viewport: playwright.devices['Pixel 2'].viewport,
    });

    page = await context.newPage();
    await page.addScriptTag({ content: `./headless-spoof.js` });
  });

  afterEach(async function() {
    await page.screenshot({ path: `screenshots/${screenshotcounter++} - ${this.currentTest.title.replace(/\s+/g, '_')}.png` });
  });

  after(async function() {
    await page.close();
    await context.close();
    await browser.close();
  });

  describe('Signing In, Joining, and Cancelling', function() {
    before(goto('/subscribe?plan=gold'));

    it('Logs in with Google', async function() {
      // Click the "Sign in with Google" Button
      const selector = 'shadow=[data-provider-id="google.com"]';
      await page.waitForSelector(selector, { visibility: 'visible' });
      const login = await page.$(selector);
      await login.click();

      await page.waitForNavigation();
      await new Promise(r => setTimeout(r, 5 * 1000));

      // Disambiguate: Which version of the Google sign-in form is it?
      const content = await page.evaluate(body => body.innerHTML, await page.$('body'));
      const isMobileLoginPage = content.includes('One account. All of Google.');

      // Fill Username
      const userNameSelector = isMobileLoginPage ? '#Email' : '#identifierId';
      await page.waitForSelector(userNameSelector, { visibility: 'visible' });
      const userNameField = await page.$(userNameSelector);
      await userNameField.fill(process.env.GMAIL_USER);
      await userNameField.press('Enter');

      // Fill Password
      const passwordSelector = isMobileLoginPage ? '#Passwd' : 'input[type="password"]';
      await page.waitForSelector(passwordSelector, { visibility: 'visible' });
      const passwordField = await page.$(passwordSelector);
      await passwordField.fill(process.env.GMAIL_PASS);
      await passwordField.press('Enter');

      await page.waitForNavigation();

      // Meanwhile, back at the ranch...
      const subscribeSelector = 'shadow=#payment h2';
      await page.waitForSelector(subscribeSelector, { visibility: 'visible' });

      const heading = await page.$eval(subscribeSelector, textContent);
      expect(heading).to.match(/Secure Payment/);
    });
  });
});

headless-spoof.js

// overwrite the `languages` property to use a custom getter
Object.defineProperty(window.navigator, 'languages', {
  get: function() {
    return ['en-US', 'en'];
  },
});

// overwrite the `plugins` property to use a custom getter
Object.defineProperty(window.navigator, 'plugins', {
  get: function() {
    // this just needs to have `length > 0`, but we could mock the plugins too
    return [1, 2, 3, 4, 5];
  },
});

const { getParameter } = WebGLRenderingContext;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
  // UNMASKED_VENDOR_WEBGL
  if (parameter === 37445)
    return 'Intel Open Source Technology Center';

  // UNMASKED_RENDERER_WEBGL
  if (parameter === 37446)
    return 'Mesa DRI Intel(R) Ivybridge Mobile ';


  return getParameter(parameter);
};

['height', 'width'].forEach(property => {
  // store the existing descriptor
  const imageDescriptor = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, property);

  // redefine the property with a patched descriptor
  Object.defineProperty(HTMLImageElement.prototype, property, {
    ...imageDescriptor,
    get: function() {
      // return an arbitrary non-zero dimension if the image failed to load
      if (this.complete && this.naturalHeight == 0)
        return 20;

      // otherwise, return the actual dimension
      return imageDescriptor.get.apply(this);
    },
  });
});

// store the existing descriptor
const elementDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight');

// redefine the property with a patched descriptor
Object.defineProperty(HTMLDivElement.prototype, 'offsetHeight', {
  ...elementDescriptor,
  get: function() {
    if (this.id === 'modernizr')
      return 1;

    return elementDescriptor.get.apply(this);
  },
});
e2e:chrome:
  image: arjun27/playwright-bionic:0.2.0
  environment: staging
  stage: e2e
  only:
    - development
  script:
    - mkdir -p screenshots
    - npx mocha -r dotenv/config 'test/*.spec.js' --bail --timeout 15000
  artifacts:
    paths:
    - screenshots
    expire_in: 1 week
    when: always

尽管有这些预防措施,我仍然在我的 CI 工作的工件中看到了这个结果:

我可以在我的测试脚本或相关的 Google 帐户中做些什么来克服这个障碍吗?

标签: javascriptgitlab-cigoogle-accountplaywright

解决方案


推荐阅读