Giter Club home page Giter Club logo

cypress-social-logins's Introduction

cypress-social-logins

cypress authentication flows using social network providers

npm version license downloads build Known Vulnerabilities Security Responsible Disclosure

** ⚠️ DISCLAIMER and LIMITATIONS ⚠️ **

This plugin doesn't work well in a CI environment, due to the anti-fraud detection mechanisms employed by the likes of Google, GitHub etc. Why? If you attempt to login from a CI machine which has different IPs, geolocation and other fingerprint identification which the account you use isn't normally attempting a login from, then this will trigger Multi Factor Authentication, CAPTCHA, or other means of confirming the identity. When those extra steps are needed, this plugin doesn't work well around them.

About

This Cypress library makes it possible to perform third-party logins (think oauth) for services such as GitHub, Google or Facebook.

It does so by delegating the login process to a puppeteer flow that performs the login and returns the cookies for the application under test, so they can be set by the calling Cypress flow for the duration of the test.

Support

Supported identity providers:

Provider Plugin name
Google GoogleSocialLogin
GitHub GitHubSocialLogin
Microsoft MicrosoftSocialLogin
Amazon AmazonSocialLogin
Facebook FacebookSocialLogin
Twitter TBD
LinkedIn TBD

Usage

  1. Call the declared task with a set of options for the social login flow interaction
  2. Set the cookies for the test flow with the help of Cypress.Cookies.defaults
  3. Copy over all or some (or none) of the local & session storage objects from puppeteer to local instance. Note: If you want to persist localStorage through all tests, see localStorage Troubleshooting below.
cy.clearCookies()

return cy.task('GoogleSocialLogin', socialLoginOptions).then(results => {
  results['cookies'].forEach(cookie => {
    if (cookie.domain.includes(cookieName)) {
      cy.setCookie(cookie.name, cookie.value, {
        domain: cookie.domain,
        expiry: cookie.expires,
        httpOnly: cookie.httpOnly,
        path: cookie.path,
        secure: cookie.secure
      })
    }
  })
  cy.window().then(window => {
    Object.keys(results.ssd).forEach(key => window.sessionStorage.setItem(key, results.ssd[key]))
    Object.keys(results.lsd).forEach(key => window.localStorage.setItem(key, results.lsd[key]))
  })
})

Options passed to the task include:

Option name Description Example
username
password
loginUrl The URL for the login page that includes the social network buttons https://www.example.com/login
loginUrlCredentials Basic Authentication credentials for the loginUrl {username: user, password: demo}
args string array which allows providing further arguments to puppeteer ['--no-sandbox', '--disable-setuid-sandbox']
headless Whether to run puppeteer in headless mode or not true
logs Whether to log interaction with the loginUrl website & cookie data false
loginSelector A selector on the page that defines the specific social network to use and can be clicked, such as a button or a link 'a[href="/auth/auth0/google-oauth2"]'
postLoginSelector A selector on the post-login page that can be asserted upon to confirm a successful login '.account-panel'
preLoginSelector a selector to find and click on before clicking on the login button (useful for accepting cookies) '.ind-cbar-right button'
preLoginSelectorIframe string a selector to find a iframe for the preLoginSelector 'div#consent iframe'
preLoginSelectorIframeDelay number delay a specific ms after click on the preLoginSelector. Pass a falsy (false, 0, null, undefined, '') to avoid completely. 2000
otpSecret Secret for generating a one-time password based on OTPLIB 'SECRET'
loginSelectorDelay delay a specific amount of time before clicking on the login button, defaults to 250ms. Pass a boolean false to avoid completely. 100
getAllBrowserCookies Whether to get all browser cookies instead of just ones with the domain of loginUrl true
isPopup boolean, is your google auth displayed like a popup true
popupDelay number, delay a specific milliseconds before popup is shown. Pass a falsy (false, 0, null, undefined, '') to avoid completely 2000
cookieDelay number, delay a specific milliseconds before get a cookies. Pass a falsy (false, 0, null,undefined,'') to avoid completely 100
postLoginClick Optional: a selector to find and click on after clicking on the login button #idSIButton9
usernameField Required for CustomizedLogin: string, a selector for the username field
usernameSubmitBtn Optional for CustomizedLogin: string, a selector for the username button
passwordField Required for CustomizedLogin: string, a selector for the password field
passwordSubmitBtn Optional for CustomizedLogin: string, a selector for password submit button
screenshotOnError Optional: will grab a screen shot if an error occurs on the username, password, or post-login page and saves in the Cypress screenshots folder. false
additionalSteps Optional: function, to define any additional steps which may be required after executing functions for username and password, such as answering security questions, PIN, or anything which may be required to fill out after username and password process. The function and this property must be defined or referenced from index.js for Cypress Plugins directory. async function moreSteps({page, options} = {}) { await page.waitForSelector('#pin_Field') await page.click('#pin_Field') }
trackingConsentSelectors Optional: selectors to find and click on after clicking the login button, but before entering details on the third-party site (useful for accepting third-party cookies e.g. Facebook login). Provide multiple if wanting to accept only essential cookies and it requires multiple clicks ['button[data-testid="cookie-policy-dialog-manage-button"]', 'button-data-testid="cookie-policy-manage-dialog-accept-button"]']
preVisitLoginUrlSetCookies Optional: array of cookies to set before visiting the loginUrl [{name: 'enable-social-login', value: 'true', domain: '.cypress.io'}]

Install

Install the plugin as a dependency

npm install --save-dev cypress-social-logins

Import the plugin

Import the cypress-social-logins plugin definition for the specific social network login you are interested of, and declare a task that performs the login.

Example:

const {GoogleSocialLogin} = require('cypress-social-logins').plugins

module.exports = (on, config) => {
  on('task', {
    GoogleSocialLogin: GoogleSocialLogin
  })
}

Using the social login

Once the Cypress task is defined we can expose a test case that makes use of it. The task will accept an options object with the username, password and other configurations that need to be specified so that the task can navigate through the page properly.

Once the task has completed it will return the list of cookies from the new page. Most likely these cookies need to be set for the rest of the sessions in the test flow, hence the example code showing the case for Cypress.Cookies.defaults.

describe('Login', () => {
  it('Login through Google', () => {
    const username = Cypress.env('googleSocialLoginUsername')
    const password = Cypress.env('googleSocialLoginPassword')
    const loginUrl = Cypress.env('loginUrl')
    const cookieName = Cypress.env('cookieName')
    const socialLoginOptions = {
      username: username,
      password: password,
      loginUrl: loginUrl,
      headless: true,
      logs: false,
      loginSelector: '[href="/auth/auth0/google-oauth2"]',
      postLoginSelector: '.account-panel'
    }

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
      cy.clearCookies()

      const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        })

        Cypress.Cookies.defaults({
          preserve: cookieName
        })
      }
    })
  })
})

Defining custom login

1 Alternative When you need to use social logins which aren't supported by this plugin you can make use of the baseLoginConnect() function that is exported as part of the plugin like so:

const {baseLoginConnect} = require('cypress-social-logins').plugins

module.exports = (on, config) => {
  on('task', {
    customLogin(options) {
      async function typeUsername({page, options} = {}) {
        await page.waitForSelector('input[id="username"]')
        await page.type('input[id="username"]', options.username)
      }

      async function typePassword({page, options} = {}) {
        await page.waitForSelector('input[id="password"]')
        await page.type('input[id="password"]', options.password)
        await page.click('button[id="_submit"]')
      }

      return baseLoginConnect(typeUsername, typePassword, null, options)
    }
  })
}

2 Alternative You can also use the CustomizedLogin function and just provide the selectors inside the options object to pass into the function. Properties usernameField and passwordField are required, otherwise the function will throw an Error with a message for requirements. Properties usernameSubmitBtn and passwordSubmitBtn are optional. (It is recommended to define passwordSubmitBtn to help proceed login flow.)

Test file -

describe('Login', () => {
  it('Login through Google', () => {
    const username = Cypress.env('googleSocialLoginUsername')
    const password = Cypress.env('googleSocialLoginPassword')
    const loginUrl = Cypress.env('loginUrl')
    const cookieName = Cypress.env('cookieName')
    const socialLoginOptions = {
      username,
      password,
      loginUrl,
      usernameField: '#input_username',
      passwordField: '#input_password',
      passwordSubmitBtn: '#login_btn_sign',
      headless: true,
      logs: false,
      loginSelector: '[href="/auth/auth0/google-oauth2"]',
      postLoginSelector: '.account-panel'
    }

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
      cy.clearCookies()

      const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        })

        Cypress.Cookies.defaults({
          preserve: cookieName
        })
      }
    })
  })
})

Plugns -

/**
 * @type {Cypress.PluginConfig}
 */
const {CustomizedLogin} = require('cypress-social-logins').plugins

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  on('task', {
    customizedLogin: options => {
      return CustomizedLogin(options)
    }
  })
}

Using AmazonSocialLogin with OneTimePassword

You need an Amazon account with activated 2fa. The QR-Code is provided by Amazon and contains a SECRET to calculate an OTP. This is mandatory due the enforcement of 2fa of new amazon-accounts. SMS or E-Mail is not supported. You can extract the Secret from the QR-Code:

otpauth://totp/Amazon%3ASomeUser%40Example?secret=IBU3VLM........&issuer=Amazon

You need to set up the account in Amazon with GoogleAuthenticator or any password-manager which supports OTP. Further information here: https://www.amazon.com/gp/help/customer/display.html?nodeId=GE6SLZ5J9GCNRW44

Adding AdditionalSteps to login work flow

If there more steps to your login work-flow after submitting username and pass, you can define your functions for these extra steps, then assign them to the options.additionalSteps property in Cypress plugins file.

/**
 * @type {Cypress.PluginConfig}
 */
async function fewMoreSteps({page, options} = {}) {
  // ... define steps
}

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  on('task', {
    customizedLogin: options => {
      options.additionalSteps = fewMoreSteps

      return CustomizedLogin(options)
    }
  })
}

Defining custom login

When you need to use social logins which aren't supported by this plugin you can make use of the baseLoginConnect() function that is exported as part of the plugin like so:

const {baseLoginConnect} = require('cypress-social-logins').plugins

module.exports = (on, config) => {
  on('task', {
    customLogin(options) {
      async function typeUsername({page, options} = {}) {
        await page.waitForSelector('input[id="username"')
        await page.type('input[id="username"', options.username)
      }

      async function typePassword({page, options} = {}) {
        await page.waitForSelector('input[id="password"]')
        await page.type('input[id="password"]', options.password)
        await page.click('button[id="_submit"]')
      }

      return baseLoginConnect(typeUsername, typePassword, null, options)
    }
  })
}

Using AmazonSocialLogin with OneTimePassword

You need an Amazon account with activated 2fa. The QR-Code is provided by Amazon and contains a SECRET to calculate an OTP. This is mandatory due the enforcement of 2fa of new amazon-accounts. SMS or E-Mail is not supported. You can extract the Secret from the QR-Code:

otpauth://totp/Amazon%3ASomeUser%40Example?secret=IBU3VLM........&issuer=Amazon

You need to set up the account in Amazon with GoogleAuthenticator or any password-manager which supports OTP. Further information here: https://www.amazon.com/gp/help/customer/display.html?nodeId=GE6SLZ5J9GCNRW44

Troubleshooting

Timeout while trying to enter username

Make sure you are providing the plugin with the username or password in the options when instantiating it. If you're passing it via environment variables then the plugin will look for these two: CYPRESS_googleSocialLoginUsername and CYPRESS_googleSocialLoginPassword

If your application uses popup auth, make sure you are providing isPopup: true configuration parameter.

Timeout error with Selectors

Puppeteer uses document.querySelectors. If you use selectors such as jQuery, you might face timeout errors because Puppeteer may not understand.

You can check these links to get examples for valid selectors: document.querySelector() CSS Selectors

Failed to launch the browser process

If you're getting an error on a Linux server such as:

Error: Failed to launch the browser process!
[768:768:0423/165641.025850:ERROR:zygote_host_impl_linux.cc(89)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.
TROUBLESHOOTING:

You should pass the argument --no-sandbox to the plugin as extra arguments.

localStorage isn't persisting through all tests

If you find that lsd is not persisting through tests (useful if you need a JWT from SSO in order to login before each test) using the default implementation above, then you can utilize the package cypress-localstorage-commands (https://www.npmjs.com/package/cypress-localstorage-commands).

To use:

npm install --save-dev cypress-localstorage-commands

import 'cypress-localstorage-commands'

before(() => {
  describe('Login through Google', () => {
    const username = Cypress.env('googleSocialLoginUsername')
    const password = Cypress.env('googleSocialLoginPassword')
    const loginUrl = Cypress.env('loginUrl')
    const localStorageItem = Cypress.env('lsdItemName')
    const socialLoginOptions = {
      username: username,
      password: password,
      loginUrl: loginUrl,
      headless: true,
      logs: false,
      loginSelector: '[href="/auth/auth0/google-oauth2"]',
      postLoginSelector: '.account-panel'
    }

    // Clears localStorage prior to getting any new localStorage items
    cy.clearLocalStorageSnapshot()

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({lsd}) => {
      // Check for localStorage item, such as a JWT or similar
      const hasLsd = Object.keys(lsd)
        .filter(item => item === localStorageItem)
        .pop()

      if (hasLsd) {
        cy.window().then(() => {
          Object.keys(lsd).forEach(key => {
            cy.setLocalStorage(key, lsd[key])
          })
        })

        // Saves a snapshot of localStorage
        cy.saveLocalStorage()
      }
    })
  })
})

// Restore the saved localStorage snapshot prior to each test
beforeEach(() => {
  cy.restoreLocalStorage()
})

// Save the localStorage snapshot after each test
afterEach(() => {
  cy.saveLocalStorage()
})

Error: module not found: "ws" from file

If you're getting an error message such as:

Error: module not found: "ws" from file ..... node_modules/puppeteer/lib/WebSocketTransport.js #17

It may be due to the fact that you're requiring one of the exported plugin functions, such as GoogleSocialLogin in your spec file in addition to requiring it in cypress/plugins/index.js. Remove it from your spec file, or from a support/index.js and make sure you export the GoogleSocialLogin function as a task only from the /plugins/index.js file.

See discussion about in this issue.

Amazon OTP not accepted

Please be aware of proper time on your machine. Make sure you are using ntp to be in sync.

additionalSteps not a function

Please avoid defining your additionalSteps function inside your test file. It will cause errors when you pass your options object through cy.task().

If you also have cases with multiple scenarios, such as having both cases to enter PIN or secuirty after password or enter usual username and password login flow without extra steps, you can add a property in the options object as an indicater which additional functions you wish to apply.

Example:

/**
 * @type {Cypress.PluginConfig}
 */
async function fewMoreStepsPin({page, options} = {}) {
  // ... define steps to enter PIN
}

async function fewMoreStepsSecurityQ({page, option} = {}) {
  // ... define steps to enter secuirty question
}

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  on('task', {
    customizedLogin: options => {
      if (options.moreSteps === 'pin') {
        // assign options.addtionalSteps pin function
        options.additionalSteps = fewMoreStepsPin
      } else if (options.moreSteps === 'securityQ') {
        // assign options.additionalSteps securityQ
        options.additionalSteps = fewMoreStepsSecurityQ
      }
      return CustomizedLogin(options)
    }
  })
}

Author

Liran Tal [email protected]

cypress-social-logins's People

Contributors

24601 avatar bvanderneut avatar eschaefer avatar fkropfhamer avatar fonata avatar fredlavoie avatar gremz avatar groov1kk avatar inglkruiz avatar invisibleexo avatar lirantal avatar michellelievsay avatar mirzamusic avatar mpoehler avatar pascalmh avatar paulmwhite avatar pwilms avatar rabrown1986 avatar raptatinha avatar rizkit avatar sanketphansekar avatar satishchilkaka avatar snyk-bot avatar timlehner avatar vfedrunov avatar vikmaksymenko avatar vtcaregorodtcev avatar wadehammes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cypress-social-logins's Issues

Error: module not found: "ws" from file ..... node_modules/puppeteer/lib/WebSocketTransport.js

  • Library Version:
    Screen Shot 2020-02-06 at 3 34 36 PM

  • OS: MacOS v 10.14.6

  • Node.js Version: v10.15.0

What causes this error?

  1. after installing the plugin, using the following:
    'yarn add cypress-social-logins'

  2. and invoking the cypress test runner:
    'CYPRESS_BASE_URL="https://accounts.google.com" yarn cypress'

  3. I'm getting the following error;
    Error: module not found: "ws" from file ...... /node_modules/puppeteer/lib/WebSocketTransport.js

  4. example code is:

module.exports = (on, config) => {
  on('task', {
    GoogleSocialLogin: GoogleSocialLogin
  })
}

describe('Login', () => {
  it('Login through Google', () => {
    const username = ('XYZ1234');
    const password = ('XYZ1234');
    const loginUrl = ('https://google.accounts.com/ServiceLogin')
    const cookieName = ('cookieName')

    const socialLoginOptions = {
      username,
      password,
      loginUrl,
      headless: false,
      logs: true,
      loginSelector: 'a[href="/auth/auth0/google-oauth2"]',
      postLoginSelector: '.account-panel'
    }

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
      cy.clearCookies()

      const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        })

        Cypress.Cookies.defaults({
          whitelist: cookieName
        })
      }
    })
  })
})




The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two-Factor Authentication, make configure the auth-only level is supported. semantic-release cannot publish with the default auth-and-writes level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot 📦🚀

Runnning the plugin on a linux machine gives me an error

First up, big thanks for this awesome plugin. It definitely opens up a lot of options for me with Cypress. I am currently using bitbucket pipelines to run my tests and when I tried to run my test that uses the plugin with Bitbucket I am getting the error below. Any ideas on how to fix or workaround this would be much appreciated.

Error: Failed to launch the browser process!
[768:768:0423/165641.025850:ERROR:zygote_host_impl_linux.cc(89)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.
TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md

Options Object new Function Property isn't getting Invoked When Called Upon in baseLoginConnect Function

I'm having trouble trying to invoke a function I created as a property for the options Object. I'm trying to add and test a check for an additional check whether user has added a property called: additionalSteps in the options Object. The purpose for additionalSteps property is for the User to define an async functions , which passes in the page and options objects as parameters, then can be used to define whatever steps a User may need to do after the typePassword function. (Ex: Some sign ins steps might require a PIN, Security Question, etc.)
Currently, I have the check for options.additionalSteps right after typePassword function. If condition is true, I would then invoke the additonalSteps function. Problem is that it doesn't appear the function is getting invoked and code moves on to the options.optSecret check. I put a boolean variable within the if condition to determine if the function did get invoked, and the actions are being performed within a second before Chromium session closes, the results show that its not getting invoked.
Initially I attempted to invoke the function as options.additionalSteps({page, options}) without luck. Second attempt was to declare an additionalSteps variable, if options does contain an additionalSteps property, assign addtionalSteps = options.additionalSteps which is then passed into parameters for baseLoginConnect function.
Is there a factor I'm missing when trying to invoke a function from a property? Looking up on topic of invoking functions, I can't see what I'm doing wrong.
(Note: I would rather try to invoke the function as options.additionalSteps instead of passing it in as another parameter, to avoid letting baseLoginConnect function have too many parameters.)
Any advise would be helpful.

Plugins.js (Pieces of Code to review would be CustomizedLogin and additionSteps check in baseLoginConnect -

/* eslint-disable no-undef */
'use strict'

const puppeteer = require('puppeteer')
const authenticator = require('otplib').authenticator

/**
 *
 * @param {options.username} string username
 * @param {options.password} string password
 * @param {options.loginUrl} string password
 * @param {options.args} array[string] string array which allows providing further arguments to puppeteer
 * @param {options.loginSelector} string a selector on the loginUrl page for the social provider button
 * @param {options.loginSelectorDelay} number delay a specific amount of time before clicking on the login button, defaults to 250ms. Pass a boolean false to avoid completely.
 * @param {options.postLoginSelector} string a selector on the app's post-login return page to assert that login is successful
 * @param {options.preLoginSelector} string a selector to find and click on before clicking on the login button (useful for accepting cookies)
 * @param {options.otpSecret} string Secret for generating a otp based on OTPLIB
 * @param {options.headless} boolean launch puppeteer in headless more or not
 * @param {options.logs} boolean whether to log cookies and other metadata to console
 * @param {options.getAllBrowserCookies} boolean whether to get all browser cookies instead of just for the loginUrl
 * @param {options.isPopup} boolean is your google auth displayed like a popup
 * @param {options.popupDelay} number delay a specific milliseconds before popup is shown. Pass a falsy (false, 0, null, undefined, '') to avoid completely
 * @param {options.cookieDelay} number delay a specific milliseconds before get a cookies. Pass a falsy (false, 0, null, undefined, '') to avoid completely.
 * @param {options.postLoginClick} string a selector to find and click on after clicking on the login button
 * @param {options.usernameField} string selector for the username field
 * @param {options.usernameSubmitBtn} string selector for the username button
 * @param {options.passwordField} string selector for the username field
 * @param {options.passwordSubmitBtn} string selector submit button
 * @param {options.additionalSteps} function any additional func which may be required for signin step after username and password
 */

function delay(time) {
  return new Promise(function(resolve) {
    setTimeout(resolve, time)
  })
}

function validateOptions(options) {
  if (!options.username || !options.password) {
    throw new Error('Username or Password missing for social login')
  }
}

async function login({page, options} = {}) {
  if (options.preLoginSelector) {
    await page.waitForSelector(options.preLoginSelector)
    await page.click(options.preLoginSelector)
  }

  await page.waitForSelector(options.loginSelector)

  if (options.loginSelectorDelay !== false) {
    await delay(options.loginSelectorDelay)
  }

  await page.click(options.loginSelector)
}

async function getCookies({page, options} = {}) {
  await page.waitForSelector(options.postLoginSelector)

  const cookies = options.getAllBrowserCookies
    ? await getCookiesForAllDomains(page)
    : await page.cookies(options.loginUrl)

  if (options.logs) {
    console.log(cookies)
  }

  return cookies
}

async function getLocalStorageData({page, options} = {}) {
  await page.waitForSelector(options.postLoginSelector)

  const localStorageData = await page.evaluate(() => {
    let json = {}
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i)
      json[key] = localStorage.getItem(key)
    }
    return json
  })
  if (options.logs) {
    console.log(localStorageData)
  }

  return localStorageData
}

async function getSessionStorageData({page, options} = {}) {
  await page.waitForSelector(options.postLoginSelector)

  const sessionStorageData = await page.evaluate(() => {
    let json = {}
    for (let i = 0; i < sessionStorage.length; i++) {
      const key = sessionStorage.key(i)
      json[key] = sessionStorage.getItem(key)
    }
    return json
  })
  if (options.logs) {
    console.log(sessionStorageData)
  }

  return sessionStorageData
}

async function getCookiesForAllDomains(page) {
  const cookies = await page._client.send('Network.getAllCookies', {})
  return cookies.cookies
}

async function finalizeSession({page, browser, options} = {}) {
  await browser.close()
}

async function waitForMultipleSelectors(selectors, options, page) {
  const navigationOutcome = await racePromises(
    selectors.map(selector => page.waitForSelector(selector, options))
  )
  return selectors[parseInt(navigationOutcome)]
}

async function racePromises(promises) {
  const wrappedPromises = []
  let resolved = false
  promises.map((promise, index) => {
    wrappedPromises.push(
      new Promise(resolve => {
        promise.then(
          () => {
            resolve(index)
          },
          error => {
            if (!resolved) {
              throw error
            }
          }
        )
      })
    )
  })
  return Promise.race(wrappedPromises).then(index => {
    resolved = true
    return index
  })
}

async function baseLoginConnect(typeUsername, typePassword, otpApp, authorizeApp, additionalSteps, postLogin, options) {
  validateOptions(options)

  const launchOptions = {headless: !!options.headless}

  if (options.args && options.args.length) {
    launchOptions.args = options.args
  }

  const browser = await puppeteer.launch(launchOptions)
  let page = await browser.newPage()
  let originalPageIndex = 1
  await page.setViewport({width: 1280, height: 800})
  await page.setExtraHTTPHeaders({
    'Accept-Language': 'en-USq=0.9,enq=0.8'
  })
  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0 Win64 x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
  )

  await page.goto(options.loginUrl)
  await login({page, options})

  // Switch to Popup Window
  if (options.isPopup) {
    if (options.popupDelay) {
      await delay(options.popupDelay)
    }
    const pages = await browser.pages()
    // remember original window index
    originalPageIndex = pages.indexOf(
      pages.find(p => page._target._targetId === p._target._targetId)
    )
    page = pages[pages.length - 1]
  }

  await typeUsername({page, options})
  await typePassword({page, options})
  let additionsFound = false
  if (options.additionalSteps && additionalSteps != null) {
    console.log('perform additional steps')
    await additionalSteps({page, options})
    additionsFound = true
  }

  if (options.otpSecret && otpApp) {
    await otpApp({page, options})
  }

  if (options.authorize) {
    await authorizeApp({page, options})
  }

  // Switch back to Original Window
  if (options.isPopup) {
    if (options.popupDelay) {
      await delay(options.popupDelay)
    }
    const pages = await browser.pages()
    page = pages[originalPageIndex]
  }

  if (options.postLoginClick) {
    await postLogin({page, options})
  }

  if (options.cookieDelay) {
    await delay(options.cookieDelay)
  }

  const cookies = await getCookies({page, options})
  const lsd = await getLocalStorageData({page, options})
  const ssd = await getSessionStorageData({page, options})
  await finalizeSession({page, browser, options})

  return {
    cookies,
    lsd,
    ssd,
    additionsFound
  }
}

module.exports.baseLoginConnect = baseLoginConnect

module.exports.GoogleSocialLogin = async function GoogleSocialLogin(options = {}) {
  const typeUsername = async function({page, options} = {}) {
    await page.waitForSelector('input#identifierId[type="email"]')
    await page.type('input#identifierId[type="email"]', options.username)
    await page.click('#identifierNext')
  }

  const typePassword = async function({page, options} = {}) {
    let buttonSelectors = ['#signIn', '#passwordNext', '#submit']

    await page.waitForSelector('input[type="password"]', {visible: true})
    await page.type('input[type="password"]', options.password)

    const buttonSelector = await waitForMultipleSelectors(buttonSelectors, {visible: true}, page)
    await page.click(buttonSelector)
  }

  const postLogin = async function ({page, options} = {}) {
    await page.waitForSelector(options.postLoginClick)
    await page.click(options.postLoginClick)
  }

  return baseLoginConnect(typeUsername, typePassword, null, null, postLogin, options)
}

module.exports.GitHubSocialLogin = async function GitHubSocialLogin(options = {}) {
  const typeUsername = async function({page, options} = {}) {
    await page.waitForSelector('input#login_field')
    await page.type('input#login_field', options.username)
  }

  const typePassword = async function({page, options} = {}) {
    await page.waitForSelector('input#password', {visible: true})
    await page.type('input#password', options.password)
    await page.click('input[type="submit"]')
  }

  const authorizeApp = async function({page, options} = {}) {
    await page.waitForSelector('button#js-oauth-authorize-btn', {visible: true})
    await page.click('button#js-oauth-authorize-btn', options.password)
  }

  const postLogin = async function ({page, options} = {}) {
    await page.waitForSelector(options.postLoginClick)
    await page.click(options.postLoginClick)
  }

  return baseLoginConnect(typeUsername, typePassword, null, authorizeApp, postLogin, options)
}

module.exports.MicrosoftSocialLogin = async function MicrosoftSocialLogin(options = {}) {
  const typeUsername = async function({page, options} = {}) {
    await page.waitForSelector('input[type="email"]')
    await page.type('input[type="email"]', options.username)
    await page.click('input[type="submit"]')
  }

  const typePassword = async function({page, options} = {}) {
    await delay(5000)

    await page.waitForSelector('input[type="password"]', {visible: true})
    await page.type('input[type="password"]', options.password)
    await page.click('input[type="submit"]')
  }

  const authorizeApp = async function({page, options} = {}) {
    await page.waitForSelector('button#js-oauth-authorize-btn', {visible: true})
    await page.click('button#js-oauth-authorize-btn', options.password)
  }

  const postLogin = async function ({page, options} = {}) {
    await page.waitForSelector(options.postLoginClick)
    await page.click(options.postLoginClick)
  }

  return baseLoginConnect(typeUsername, typePassword, null, authorizeApp, postLogin, options)
}

module.exports.AmazonSocialLogin = async function AmazonSocialLogin(options = {}) {
  const typeUsername = async function({page, options} = {}) {
    await page.waitForSelector('#ap_email', {visible: true})
    await page.type('#ap_email', options.username)
  }

  const typePassword = async function({page, options} = {}) {
    let buttonSelectors = ['#signInSubmit']

    await page.waitForSelector('input[type="password"]', {visible: true})
    await page.type('input[type="password"]', options.password)

    const buttonSelector = await waitForMultipleSelectors(buttonSelectors, {visible: true}, page)
    await page.click(buttonSelector)
  }

  const otpApp = async function({page, options} = {}) {
    let buttonSelectors = ['#auth-signin-button']

    await page.waitForSelector('#auth-mfa-otpcode', {visible: true})
    await page.type('#auth-mfa-otpcode', authenticator.generate(options.otpSecret))

    const buttonSelector = await waitForMultipleSelectors(buttonSelectors, {visible: true}, page)
    await page.click(buttonSelector)
  }

  return baseLoginConnect(typeUsername, typePassword, otpApp, null, null, options)
}

module.exports.CustomizedLogin = async function CustomizedLogin(options = {}) {
  if (options.usernameField && options.passwordField) {
    const typeUsername = async function({page, options} = {}) {
      await page.waitForSelector(options.usernameField, {visible: true})
      await page.type(options.usernameField, options.username)
      if (options.usernameSubmitBtn) {
        await page.click(options.usernameSubmitBtn)
      }
    }
    const typePassword = async function({page, options} = {}) {
      await page.waitForSelector(options.passwordField, {visible: true})
      await page.type(options.passwordField, options.password)
      if (options.passwordSubmitBtn) {
        await page.click(options.passwordSubmitBtn)
      }
    }
    let additionalSteps = null
    if (options.additionalSteps) {
      additionalSteps = options.additionalSteps
    }
    const postLogin = async function ({page, options} = {}) {
      await page.waitForSelector(options.postLoginClick)
      await page.click(options.postLoginClick)
    }

    return baseLoginConnect(typeUsername, typePassword, null, null, additionalSteps, postLogin, options)
  } else {
    throw new Error('Please review your option properties. Propeties usernameField and passwordField are required as type String.')
  }
}

test code -

it.only('Login with custom function', () => {
    const username = Cypress.env('steamUserName')
    const password = Cypress.env('steamUserNamePW')
    const loginUrl = Cypress.env('loginUrl')
    const loginSelector = Cypress.env('loginSelector')
    const cookieName = Cypress.env('cookieName')
    const socialLoginOptions = {
      username,
      password,
      loginUrl,
      // Add username/pw fields and buttons and addtional steps
      usernameField: '#input_username',
      passwordField: '#input_password',
      passwordSubmitBtn: '#login_btn_signin',
      additionalSteps: async function({page, options} = {}) {
        console.log('This is the addtional step...')
        await page.waitForSelector('#header_notification_link', {visible: true})
        await page.click('#header_notification_link')
      },
      isPopup: true,
      popupDelay: 6000,
      logs: true,
      headless: false,
      loginSelector: loginSelector,
      postLoginClick: '#account_pulldown',
      postLoginSelector: '#account_dropdown div.popup_menu a.popup_menu_item:first-of-type'
    }

    cy.log(socialLoginOptions)

    return cy.task('customizedLogin', socialLoginOptions, {timeout: 300000}).then(({cookies, lsd, ssd, additionsFound}) => {
      cy.log(additionsFound)
      cy.clearCookies()
      const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        })

        cy.log(cookie)

        Cypress.Cookies.defaults({
          preserve: cookieName
        })
      }
    })
  })

Support hidden login links

Since this plugin doesn't support pop-ups, and our login button uses a pop-up, we added a hidden login button that uses a redirect to make testing work. But since that button was hidden (positioned absolutely off the page), puppeteer's click function failed (since it wants to scroll
the hidden login link into view, and fails when it can't). I couldn't find an easy way to make users
not see the button, but let puppeteer see it.

I was able get around this by forking the plugin and changing the login function's page.click to:
await page.$eval(options.loginSelector, e => e.click())

Is there a better approach here that I could use?

facebookSocialLogin doesnt enter the complete facebook username

I am using cypress-social-logins to authenticate a facebook user to my website exporting the FacebookSocialLogin task from plugins/index.ts. However, the username gets cut short as you can see in the screenshot below. My test looks like below:

`it('Login through Facebook', () => {

  const username = Cypress.env('fbSocialLoginUsername');
  const password = Cypress.env('fbSocialLoginPassword');
  const loginUrl = Cypress.env('loginUrl');
  const cookieName = Cypress.env('cookieName');
  const socialLoginOptions = {
    username: username,
    password: password,
    loginUrl: loginUrl,
    headless: false,
    logs: false,
    loginSelector: 'a[href="/users/auth/facebook?origin=https%3A%2F%2Fwebapp.preprod.applyboard.dev%2F%3F"]',
    postLoginClick: "facebook.button",
    postLoginSelector: ".account"
  };`

Facebook_incompleteString

Github Social Login example

Hi there, I am trying to implement Github social login but not sure what will be the loginSelector should I put in it. Also, there is not a single example I find on the internet regarding Github login tests. Please if anyone implements this in their tests, please let me know.

Thanks

endless loading (gmail)

Hello !

Thank to your plugins, I managed to connect to my gmail account. But for an unknow reason, my test "stop" (timeout of cypress) because the loading of gmail never finish.

I don't know if you had a similar issue or some things that would approach it.

I can write my email adress, then my password without problem, but when i log in :

image

Btw if i click in the url of cypress, firefox open a new windows and here the loading is good and i can saw my email. But I need my test to do it since i need to check something in it.

So idk if you know where can be the problem ? (If you want something from my code, just say it i don't know what you might need)

Thank you

update : In fact, i think i didn’t used the plugins well. But now (if i’m right) i have a different problem.

I use as a loginUrl for the const socialLoginOptions, the url of gmail. But since i want my test to « start » in the windows of the gmail log in, i assume that i don’t need any loginSelector. But if i don’t have a loginSelector, the test doesn’t work.

So to be honest i’m still a bit lost lol

Captcha when running on CI

Hi there,

your library works just fine on my own machine.

However, when running on the CI, Google asks "me" to enter the captcha code after "I" enter the email.

I know that your library is not possible to control that or bypass it but I wonder if you've encountered that yourself and how did you go about it. (If, e. g., it's somehow possible to disable in somewhere in the Google Developer Console or something similar.)

Feel free to close the issue if you don't have any ideas :) I know that this issue is not really actionable and a little bit off-topic.

Thanks!

Any reason you didn't use ES6 import syntax in plugins/index.js? I think that's a Cypress limitation but just want to confirm

First of all great implementation for the Google SSO workaround @lirantal !!!
This is a more question than issue or feature request:
when you required this plugin inside plugins/index.js did you purposely not use the ES6 import syntax before of the Cypress' limitation?

const {GoogleSocialLogin} = require('cypress-social-logins').plugins

module.exports = (on, config) => {
  on('task', {
    GoogleSocialLogin: GoogleSocialLogin
  })
}

now I tried to convert the const {GoogleSocialLogin} = require('cypress-social-logins').plugins to ES6 like this import { GoogleSocialLogin } from 'cypress-social-logins'; but Cypress GUI runner threw error like this:

An unexpected error occurred
The plugins file is missing or invalid.
Your pluginsFile is set to /.../web/test/cypress/plugins/index.js, but either the file is missing, it contains a syntax error, or threw an error when required. The pluginsFile must be a .js or .coffee file.
Please fix this, or set pluginsFile to false if a plugins file is not necessary for your project.

Did you run into this issue when you were implementing this plugin?

Headless: true does not work

Hello,

If I run it with:

headless: false

Everything works fine. But if I run it with

headless: true

I get this error message

CypressError: cy.task('GoogleSocialLogin') failed with the following error:

> Error: No node found for selector: #identifierNext
    at assert (C:\git\Independer\E2ETests\Cypress\node_modules\puppeteer\lib\helper.js:283:11)
    at DOMWorld.click (C:\git\Independer\E2ETests\Cypress\node_modules\puppeteer\lib\DOMWorld.js:366:5)
    at typeUsername (C:\git\Independer\E2ETests\Cypress\node_modules\cypress-social-logins\src\Plugins.js:70:3)
    at GoogleSocialLogin (C:\git\Independer\E2ETests\Cypress\node_modules\cypress-social-logins\src\Plugins.js:28:3)

  -- ASYNC --
    at Frame.<anonymous> (C:\git\Independer\E2ETests\Cypress\node_modules\puppeteer\lib\helper.js:111:15)
    at Page.click (C:\git\Independer\E2ETests\Cypress\node_modules\puppeteer\lib\Page.js:1067:29)
    at typeUsername (C:\git\Independer\E2ETests\Cypress\node_modules\cypress-social-logins\src\Plugins.js:70:14)
    at GoogleSocialLogin (C:\git\Independer\E2ETests\Cypress\node_modules\cypress-social-logins\src\Plugins.js:28:3)

No qlue what is going wrong in headless mode. Maybe you know
Bjorn

Retrieve cookies from multiple domains

Great plugin! This is a big factor in our choice to work with cypress.

Our app sets cookies in multiple domains as part of its auth process. Using this plugin we're able to get the required cookies for our own domain, but not the others we need.

We'd love to see an option to either get all cookies (for all domains) or to optionally provide a list of additional domains to get cookies from.

Is there an existing way to accomplish this that I'm missing?

Missing cookie name in returned data

With my app, after the user signs in with Google, there are 3 cookies set: id, G_AUTHUSER_H, G_ENABLED_IDPS. But cookie name (id) is missing in return data. I tried to increase the param: cookieDelay: 50000 but the issue still happens. Please help me check.

Thanks

WebSocket is not open: readyState 3 (CLOSED)*

I tried to use this plugin, but what I observed (running headless = false) is when I am navigated to my test page, then element corresponding to postLoginSelector is clicked and google login window appears. At this point login window is closed and test fails with error WebSocket is not open: readyState 3 (CLOSED)
Looking at the Stack Trace I can see this error is being fired by puppeteer on line 210 const pages = await browser.pages()
Any idea what the issue could be and how to rectify this please?

"cypress-social-logins": "^1.11.2",

describe('Login page', () => {
    before(() => {
      cy.visit('/')
      
    })
    it('Login through Google', () => {
      const username = 'my test name'
      const password = 'and password'
      const loginUrl = Cypress.config().baseUrl
      const cookieName = 'authCookie'
      const socialLoginOptions = {
        username: username,
        password: password,
        loginUrl: loginUrl,
        headless: false,
        loginSelectorDelay : 2000,
        otpSecret: 'otpSecretValueInHere',
        loginSelector: 'button[type="button"]',
        postLoginSelector: '[data="header-logo"]',
        isPopup: true,
        popupDelay: 10000
      }
  
      return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
        cy.clearCookies()
  
        const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
        if (cookie) {
          cy.setCookie(cookie.name, cookie.value, {
            domain: cookie.domain,
            expiry: cookie.expires,
            httpOnly: cookie.httpOnly,
            path: cookie.path,
            secure: cookie.secure
          })
  
          Cypress.Cookies.defaults({
            preserve: cookieName
          })
        }
      })
    })
  })

Cannot Complete Google Oath Sign-on Using Cypress-social-login

Hello Team,

I'm unable to login into my client that uses Google Oauth as a sign-on. Below are steps :

  1. When the automation job kicks off, it initially navigates to google accounts sign-on.
  2. Using cypress-social-login, it can login fine into the google account.
  3. After google login, it goes to my client page that has a button for log-in (Assuming it had already logged in from previous window) it should now let the login pass.
  4. Instead I see error as attached in screenshot below.

Error

Platform : MacOS Catalina (10.15.7)
Browser : Electron 87.
Cypess : 6.6.0 / 6.7.0 / 6.7.1

This issue is intermittent but of the times It occurs about 6 or 7 out of 10, causing our builds to fail. Any help on where to look at, how to debug, Greatly appreciate.

Google Oauth timeout error

Hello there!
I already saw some similar issues, but mine is a bit different.

So my react app uses Google JS Client library to handle the OAuth 2.0 flow. And I don't use any custom cookies etc, google handles things for me (so to be honest I don't know where and which data window needs to have to be in a logged-in state).
I want to test the application, but basically, I need to login first to access anything.
And I decided to try this plugin.

First of all, it throws the same timeout error(that email field is not found). I investigated a lot, and now I managed to have the login process running but only in not headless mode.
If headless mode is true, then test fails with the same time out error, and popupDelay doesn't help.

image

Now another thing, since I don't have any custom cookies, and google handles things for me, I see that with this plugin I still cannot do much, because probably I need to have localStorage and sessionsStorage from 'accounts.google.com' domain. Is this something doable? (I am not very familiar neither with cypress nor with puppeteer) And currently, at least in not headless mode I see that 'localStorage' and sessionsStorage are empty objects after task is done ((

google social login Secure issue

The password window does not appear after entering the ID.

image

So cypress can't find the password input.

image

This is my code

describe('Login', () => {
  it('Login through Google', () => {
    const username = Cypress.env('googleSocialLoginUsername')
    const password = Cypress.env('googleSocialLoginPassword')
    const loginUrl = Cypress.env('loginUrl')
    const cookieName = Cypress.env('cookieName')
    const socialLoginOptions = {
      username: username,
      password: password,
      loginUrl: loginUrl,
      usernameSubmitBtn: '#identifierNext > div > button',
      headless: false,
      logs: false,
      loginSelector: '[href="/accounts/google/login/?process=login"]',
      postLoginSelector: '.account-panel'
    }
    
    Cypress.log({
      consoleProps: () => {
        return {
          'socialLoginOptions': socialLoginOptions
        }
      }
    })

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
      cy.clearCookies()

      const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        })

        Cypress.Cookies.defaults({
          preserve: cookieName
        })
      }
    })
  })
})

Optional social login selector

Sometimes, you'll get a social login page during redirection actions from specific links. In these cases you don't need to click any social provider buttons at all. But as loginSelector is not optional, in cases of redirections you have to fill it with any fake locators.

So, I've made a fix which makes socialLogin selector optional.

Timeout Error Occurring during loginSelector Step in the Login Function

I would say this is more of issue on Puppeteer's Side, but wanted to reach out here first before further investigation and seeking help elsewhere.
I getting stuck at login function during the step for page.waitForSelector(options.loginSelector). It keeps getting a timeout error; even after I extended the Timeout by 3 times the default, or use a different selector for that function. Chromium does pop appear and go to correct loginUrl. I'm currently stumped on what I'm missing or haven't considered when setting value for loginSelector Property. (I almost wonder if there should be an if Condition for that line, in case loginSelector is a link to public Url to login.)

Here what I have for Plugins.js:
`/* eslint-disable no-undef */
'use strict'

const puppeteer = require('puppeteer')

/**
*

  • @param {options.username} string username
  • @param {options.password} string password
  • @param {options.loginUrl} string password
  • @param {options.args} array[string] string array which allows providing further arguments to puppeteer
  • @param {options.loginSelector} string a selector on the loginUrl page for the social provider button
  • @param {options.loginSelectorDelay} number delay a specific amount of time before clicking on the login button, defaults to 250ms. Pass a boolean false to avoid completely.
  • @param {options.postLoginSelector} string a selector on the app's post-login return page to assert that login is successful
  • @param {options.preLoginSelector} string a selector to find and click on before clicking on the login button (useful for accepting cookies)
  • @param {options.headless} boolean launch puppeteer in headless more or not
  • @param {options.logs} boolean whether to log cookies and other metadata to console
  • @param {options.getAllBrowserCookies} boolean whether to get all browser cookies instead of just for the loginUrl
  • @param {options.isPopup} boolean is your google auth displayed like a popup
  • @param {options.popupDelay} number delay a specific milliseconds before popup is shown. Pass a falsy (false, 0, null, undefined, '') to avoid completely
  • @param {options.cookieDelay} number delay a specific milliseconds before get a cookies. Pass a falsy (false, 0, null, undefined, '') to avoid completely.
  • @param {options.postLoginClick} string a selector to find and click on after clicking on the login button
  • @param {options.usernameField} string selector for the username field
  • @param {options.usernameSubmitBtn} string selector for the username button
  • @param {options.passwordField} string selector for the username field
  • @param {options.passwordSubmitBtn} string selector submit button
  • @param {options.additionalSteps} function any additional func which may be required for signin
    */

function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
})
}

function validateOptions(options) {
if (!options.username || !options.password) {
throw new Error('Username or Password missing for social login')
}
}

async function login({page, options} = {}) {
if (options.preLoginSelector) {
await page.waitForSelector(options.preLoginSelector)
await page.click(options.preLoginSelector)
}

await page.waitForSelector(options.loginSelector, {timeout: 20000})

if (options.loginSelectorDelay !== false) {
await delay(options.loginSelectorDelay)
}

await page.click(options.loginSelector)
}

async function getCookies({page, options} = {}) {
await page.waitForSelector(options.postLoginSelector)

const cookies = options.getAllBrowserCookies
? await getCookiesForAllDomains(page)
: await page.cookies(options.loginUrl)

if (options.logs) {
console.log(cookies)
}

return cookies
}

async function getLocalStorageData({page, options} = {}) {
await page.waitForSelector(options.postLoginSelector)

const localStorageData = await page.evaluate(() => {
let json = {}
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
json[key] = localStorage.getItem(key)
}
return json
})
if (options.logs) {
console.log(localStorageData)
}

return localStorageData
}

async function getSessionStorageData({page, options} = {}) {
await page.waitForSelector(options.postLoginSelector)

const sessionStorageData = await page.evaluate(() => {
let json = {}
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
json[key] = sessionStorage.getItem(key)
}
return json
})
if (options.logs) {
console.log(sessionStorageData)
}

return sessionStorageData
}

async function getCookiesForAllDomains(page) {
const cookies = await page._client.send('Network.getAllCookies', {})
return cookies.cookies
}

async function finalizeSession({page, browser, options} = {}) {
await browser.close()
}

async function waitForMultipleSelectors(selectors, options, page) {
const navigationOutcome = await racePromises(
selectors.map(selector => page.waitForSelector(selector, options))
)
return selectors[parseInt(navigationOutcome)]
}

async function racePromises(promises) {
const wrappedPromises = []
let resolved = false
promises.map((promise, index) => {
wrappedPromises.push(
new Promise(resolve => {
promise.then(
() => {
resolve(index)
},
error => {
if (!resolved) {
throw error
}
}
)
})
)
})
return Promise.race(wrappedPromises).then(index => {
resolved = true
return index
})
}

async function baseLoginConnect(typeUsername, typePassword, authorizeApp, postLogin, options) {
validateOptions(options)

const launchOptions = {headless: !!options.headless}

if (options.args && options.args.length) {
launchOptions.args = options.args
}

const browser = await puppeteer.launch(launchOptions)
let page = await browser.newPage()
let originalPageIndex = 1
await page.setViewport({width: 1280, height: 800})
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-USq=0.9,enq=0.8'
})
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0 Win64 x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
)

await page.goto(options.loginUrl)
await login({page, options})

// Switch to Popup Window
if (options.isPopup) {
if (options.popupDelay) {
await delay(options.popupDelay)
}
const pages = await browser.pages()
// remember original window index
originalPageIndex = pages.indexOf(
pages.find(p => page._target._targetId === p._target._targetId)
)
page = pages[pages.length - 1]
}

await typeUsername({page, options})
await typePassword({page, options})

if (options.additionalSteps) {
await options.additionalSteps({page, options})
}

if (options.authorize) {
await authorizeApp({page, options})
}

// Switch back to Original Window
if (options.isPopup) {
if (options.popupDelay) {
await delay(options.popupDelay)
}
const pages = await browser.pages()
page = pages[originalPageIndex]
}

if (options.postLoginClick) {
await postLogin({page, options})
}

if (options.cookieDelay) {
await delay(options.cookieDelay)
}

const cookies = await getCookies({page, options})
const lsd = await getLocalStorageData({page, options})
const ssd = await getSessionStorageData({page, options})
await finalizeSession({page, browser, options})

return {
cookies,
lsd,
ssd
}
}

module.exports.baseLoginConnect = baseLoginConnect

module.exports.GoogleSocialLogin = async function GoogleSocialLogin(options = {}) {
const typeUsername = async function({page, options} = {}) {
await page.waitForSelector('input#identifierId[type="email"]')
await page.type('input#identifierId[type="email"]', options.username)
await page.click('#identifierNext')
}

const typePassword = async function({page, options} = {}) {
let buttonSelectors = ['#signIn', '#passwordNext', '#submit']

await page.waitForSelector('input[type="password"]', {visible: true})
await page.type('input[type="password"]', options.password)

const buttonSelector = await waitForMultipleSelectors(buttonSelectors, {visible: true}, page)
await page.click(buttonSelector)

}

const postLogin = async function ({page, options} = {}) {
await page.waitForSelector(options.postLoginClick)
await page.click(options.postLoginClick)
}

return baseLoginConnect(typeUsername, typePassword, null, postLogin, options)
}

module.exports.GitHubSocialLogin = async function GitHubSocialLogin(options = {}) {
const typeUsername = async function({page, options} = {}) {
await page.waitForSelector('input#login_field')
await page.type('input#login_field', options.username)
}

const typePassword = async function({page, options} = {}) {
await page.waitForSelector('input#password', {visible: true})
await page.type('input#password', options.password)
await page.click('input[type="submit"]')
}

const authorizeApp = async function({page, options} = {}) {
await page.waitForSelector('button#js-oauth-authorize-btn', {visible: true})
await page.click('button#js-oauth-authorize-btn', options.password)
}

const postLogin = async function ({page, options} = {}) {
await page.waitForSelector(options.postLoginClick)
await page.click(options.postLoginClick)
}

return baseLoginConnect(typeUsername, typePassword, authorizeApp, postLogin, options)
}

module.exports.MicrosoftSocialLogin = async function MicrosoftSocialLogin(options = {}) {
const typeUsername = async function({page, options} = {}) {
await page.waitForSelector('input[type="email"]')
await page.type('input[type="email"]', options.username)
await page.click('input[type="submit"]')
}

const typePassword = async function({page, options} = {}) {
await delay(5000)

await page.waitForSelector('input[type="password"]', {visible: true})
await page.type('input[type="password"]', options.password)
await page.click('input[type="submit"]')

}

const authorizeApp = async function({page, options} = {}) {
await page.waitForSelector('button#js-oauth-authorize-btn', {visible: true})
await page.click('button#js-oauth-authorize-btn', options.password)
}

const postLogin = async function ({page, options} = {}) {
await page.waitForSelector(options.postLoginClick)
await page.click(options.postLoginClick)
}

return baseLoginConnect(typeUsername, typePassword, authorizeApp, postLogin, options)
}

module.exports.CustomizedLogin = async function CustomizedLogin(options = {}) {
if (options.usernameField && options.passwordField) {
const typeUsername = async function({page, options} = {}) {
await page.waitForSelector(options.usernameField, {visible: true})
await page.type(options.usernameField, options.username)
if (options.usernameSubmitBtn) {
await page.click(options.usernameSubmitBtn)
}
}
const typePassword = async function({page, options} = {}) {
await page.waitForSelector(options.passwordField, {visible: true})
await page.type(options.passwordField, options.password)
if (options.passwordSubmitBtn) {
await page.click(options.passwordSubmitBtn)
}
}
return baseLoginConnect(typeUsername, typePassword, null, null, options)
} else {
return new Error('Please review your option properties. Propeties usernameField and passwordField are required as type String.')
}
}`

Cypress Plugins folder:
`///

const { CustomizedLogin } = require('../../src/Plugins')

// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

/**

  • @type {Cypress.PluginConfig}
    */

const {customizedLogin} = require('../../src/Plugins').CustomizedLogin

module.exports = (on, config) => {
// on is used to hook into various events Cypress emits
// config is the resolved Cypress config
on('task', {
customizedLogin: CustomizedLogin
})
}
`

Cypress test file:
`describe('Test Login', () => {
it('Login with custom function', () => {
// sites with extra security:
// keybank? Linkedin? RingCentral?
const username = Cypress.env('steamUserName')
const password = Cypress.env('steamUserNamePW')
const loginUrl = Cypress.env('loginUrl')
const loginSelector = Cypress.env('loginSelector')
const socialLoginOptions = {
username,
password,
loginUrl,
// Add username/pw fields and buttons and addtional steps
usernameField: '#input_username',
passwordField: '#input_password',
passwordSubmitBtn: '#login_btn_signin',
additionalSteps: async function({page, options} = {}) {
await page.waitForSelector('#language_pulldown')
await page.click('#language_pulldown')
await page.click('#language_pulldown')
},
isPopup: true,
logs: true,
headless: false,
// Selector not working
loginSelector: loginSelector
}

cy.log(socialLoginOptions)

return cy.task('customizedLogin', socialLoginOptions, {timeout: 80000}).then(({cookies}) => {
  cy.clearCookies()
  console.log(cookies)
})

})
})`

StackTrace:
at <unknown> (http://localhost:51204/__cypress/runner/cypress_runner.js:159616:20) From previous event: at Context.task (http://localhost:51204/__cypress/runner/cypress_runner.js:159598:16) From Your Spec Code: at Context.eval (webpack:///cypress/integration/loginTestNode.spec.js:31:15) From Node.js Internals: TimeoutError: waiting for selector "a.global_action_link[href="https://store.steampowered.com/login/?redir=%3Fsnr%3D1_60_4__global-header&redir_ssl=1&snr=1_4_4__global-header"]" failed: timeout 20000ms exceeded at new WaitTask (C:\Users\Rigat\Project_cypress-social-logins\node_modules\puppeteer\lib\DOMWorld.js:549:29) at DOMWorld._waitForSelectorOrXPath (C:\Users\Rigat\Project_cypress-social-logins\node_modules\puppeteer\lib\DOMWorld.js:478:23) at DOMWorld.waitForSelector (C:\Users\Rigat\Project_cypress-social-logins\node_modules\puppeteer\lib\DOMWorld.js:432:18) at Frame.waitForSelector (C:\Users\Rigat\Project_cypress-social-logins\node_modules\puppeteer\lib\FrameManager.js:627:48) at Frame.<anonymous> (C:\Users\Rigat\Project_cypress-social-logins\node_modules\puppeteer\lib\helper.js:112:24) at Page.waitForSelector (C:\Users\Rigat\Project_cypress-social-logins\node_modules\puppeteer\lib\Page.js:1122:30) at login (C:\Users\Rigat\Project_cypress-social-logins\src\Plugins.js:48:15) at baseLoginConnect (C:\Users\Rigat\Project_cypress-social-logins\src\Plugins.js:169:10) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:97:6)

Support passing args to puppeteer.launch method

Is your feature request related to a problem? Please describe.
When I try to run this on CI tools like HerokuCI or CircleCI, I'm getting following issue:

CypressError: cy.task('GoogleSocialLogin') failed with the following error:
Error: Failed to launch the browser process!
[0303/121657.962144:FATAL:zygote_host_impl_linux.cc(116)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using --no-sandbox.

Describe the solution you'd like
We need a new option to provide args to the puppeteer.launch method

Google Social Login yields 400: invalid_request (missing: response_type)

I followed the example for Google social log in at https://github.com/lirantal/cypress-social-logins

Cypress manages to open the Google Login in a new Window. However, a

Error 400: invalid_request
Required parameter is missing: response_type

appears, suggesting that the response type (e.g. "token") is missing in the request. That is a required parameter for the OAuth process. From this plugin's documentation it must not be given as a parameter to the task.

The test is running in Chrome 87

Does anyone else experience similar issues and could help? It is much appreciated! Thanks in advance

Need to capture screenshot of remote headed pop-up login

Is your feature request related to a problem? Please describe.
When running cypress-social-login with a headed pop-up remotely any errors are not caught by Cypress screen captures. Local runs may not catch CAPTCHA requests or MFA which are occurring remotely and these are difficult to verify from the normal Cypress "missing locator" error.

Describe the solution you'd like
I've created a feature which performs a screen capture that occurs on the username, password, and post-login events when there is an error.

Create an Export function for Custom Logins

Is your feature request related to a problem? Please describe.
Please describe the problem you are trying to solve.
I've been looking plugins or methods for login such as Request and Visit to provide login credentials to a website, (Steam.com in this case,) which I use to explore ways how to test or improve setup, instead of going to front end approach by finding and run send keys. Sadly, attempting to login this Steam.com using Request or Visit, doesn't appear to be working. I get a 200 response code, but upon refresh/reload, it doesn't appear the login worked.
I see you have a list of providers which can be passed into Cypress task(). After inspecting your function's to perform the login, I want to try the login approach using your plugin.
Would you be willing to provide an export function where users to can define the selectors they next to select to provide the user's own selectors to type or select submit buttons/tags?

Describe the solution you'd like
Please describe the desired behavior.
I was thinking the best way to define the selectors and buttons would be to provide more properties which are accepted by the Options property. (Ex: customUsernameSelector, customLoginSubmit, etc).

Describe alternatives you've considered
Please describe alternative solutions or features you have considered.

waiting for selector "input#identifierId[type="email"]" failed: timeout 30000ms exceeded

Hi, I am trying to test the google sign in & using this package. The issue I am facing is.

  • Whenever I run the test, I am not seeing it running in any window, as in the attached screenshot you can see that the loginSelector is clicked & it's showing error in finding email input, which means the loginSelector is available. why am I not seeing it in the cypress default runner page? & having the issue " waiting for selector "input#identifierId[type="email"]" failed". also if I make headless = false, I see a new browser tab opened with the loginSelector Button, am expecting this is not triggering because of the default popups are blocked in chromium. But again if that the case, I am just seeing the login button & the error I get is still the same "waiting for selector "input#identifierId[type="email"]" failed: timeout 30000ms exceeded".

Screenshot from 2020-10-07 14-24-32
Screenshot from 2020-10-07 14-25-28
Screenshot from 2020-10-07 14-27-04

Originally posted by @suryakant-kumar in #34 (comment)

Email is entered in application email button due to absez

Please describe the problem you are trying to solve.
My page has an email input, with 'input[type="email"]'. Immediately the sign in button is clicked, the plugin does not wait till the browser redirects rather, it starts entering the the email on my page's email field

Describe the solution you'd like
Email is typed only when clicking on login selector has completely redirected
Please describe the desired behavior.

Describe alternatives you've considered
An option to add a delay before entering the email, so that the page redirects before email is typed
Please describe alternative solutions or features you have considered.

Error: net::ERR_CERT_AUTHORITY_INVALID at https://localhost:8080/#/

Hi

I have this vue.js app which, after some hacks (creating local certificate and enabling my dev env for social auth), I could make it testable in my local environment using https://localhost:8080/.

Althought cypress chrome browser does not enter the page due to the invalid authority of my self-signed certificate.
I didn't find any issues related to that. Maybe I am missing something in my way to the tests?

Error: net::ERR_CERT_AUTHORITY_INVALID at https://localhost:8080/#/

Thanks in advance

Attach to popup generated by application rather than spawning new window

My application under test displays a popup with the Google sign-in dialog upon clicking a button inside a nested iframe, It would be great if we could use this plugin on the popup that is actually populated by the application, or have support for the loginSelector var to accommodate buttons inside iframes.

Plugin does not wait for redirection to google to be complete before entering email

My page has an email input, with 'input[type="email"]'. Immediately the sign in button is clicked, the plugin does not wait till the browser redirects rather, it starts entering the the email on my page's email field

Email is typed only when clicking on login selector has completely redirected
Please describe the desired behavior.

An option to add a delay before entering the email, so that the page redirects before email is typed
Please describe alternative solutions or features you have considered.

AdditionalSteps fails for entering google 2fa code

I am using the additionalSteps option to enter a token in google 2fa authentication. The token is generated using the totp-generator plugin. The additional steps looks like:

additionalSteps: async function moreSteps() => {cy.get('input#idvPin').type(token);}

However, GoogleSocialLogin fails because it does not acknowledge the 2fa code field and fails trying to look for my postLoginSelector. This means additionalSteps is not triggered before postLoginSelector. The error and screenshot are below. From the screenshot it is clear that the app page was never reached and the token was never entered on google 2fa input field. Still the error isnt about additionalSteps but postLoginSelector

CypressError cy.task('GoogleSocialLogin') failed with the following error: waiting for selector <postLoginSelector> failed: timeout 30000ms exceeded

Screen Shot 2021-03-16 at 10 40 37 AM

Click on social login button does not happen

Hello everyone,

I have this object:

const socialLoginOptions = {
      username,
      password,
      loginUrl: 'https://www.independer.nl/mijnindepender/inloggen',
      headless: false,
      logs: true,
      loginSelector: 'a[ue="login-met-google"]',
      postLoginSelector: '.account-panel'
    };

But what I see it that it opens de chromium browser. Navigates to https://www.independer.nl/mijnindepender/inloggen and then fill in on that page the username. We have the same naming as google does probably. So it does not click or trigger the social login button. Can it be because it's coverd by a cookie law block and it does not trigger it?

Thanks for the help!
Bjorn

Navigation failed because browser has disconnected after updating OS

After updating to Big Sur OS I'm getting Navigation failed because browser has disconnected! when running GoogleSocialLogin. It was definitely working before the update and a fresh install and code checkout on Catalina OS works for my peers. I checked that Chromium was enabled in the Firewall settings so I'm not sure why the browser would be closing unexpectedly. Any other ideas what would be causing this? Thanks!

Full stack trace:

From previous event:
    at Context.task (https://test.figure.com/__cypress/runner/cypress_runner.js:160084:16)
From Your Spec Code:
    at Context.eval (webpack:///cypress/support/commands.js:63:15)
From Node.js Internals:
    Error: Navigation failed because browser has disconnected!
      at CDPSession.<anonymous> (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/LifecycleWatcher.js:46:108)
      at CDPSession.emit (events.js:315:21)
      at CDPSession._onClosed (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/Connection.js:215:11)
      at Connection._onClose (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/Connection.js:138:16)
      at WebSocket.<anonymous> (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/WebSocketTransport.js:48:23)
      at WebSocket.onClose (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/ws/lib/event-target.js:124:17)
      at WebSocket.emit (events.js:315:21)
      at WebSocket.emitClose (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/ws/lib/websocket.js:191:11)
      at Socket.socketOnClose (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/ws/lib/websocket.js:850:16)
      at Socket.emit (events.js:315:21)
      at TCP.<anonymous> (net.js:674:13)
      at Frame.<anonymous> (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/helper.js:111:16)
      at Page.goto (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/Page.js:672:50)
      at Page.<anonymous> (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/node_modules/puppeteer/lib/helper.js:112:24)
      at baseLoginConnect (/Users/andieromero/figure/frontend-cashout-refinance/node_modules/cypress-social-logins/src/Plugins.js:191:15)
      at processTicksAndRejections (internal/process/task_queues.js:97:6)
      at Object.wrapChildPromise (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/util.js:40:24)
      at Object.wrap (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/task.js:53:9)
      at task (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_plugins.js:127:13)
      at execute (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_plugins.js:140:45)
      at EventEmitter.<anonymous> (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_plugins.js:204:6)
      at EventEmitter.emit (events.js:315:21)
      at process.<anonymous> (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/util.js:19:23)
      at process.emit (events.js:315:21)
      at process.emit (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/node_modules/source-map-support/source-map-support.js:495:22)
      at emit (internal/child_process.js:876:13)
      at processTicksAndRejections (internal/process/task_queues.js:85:22)
    From previous event:
      at Object.wrapParentPromise (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/util.js:58:13)
      at Object.task (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/index.js:116:24)
      at Object.execute (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/index.js:201:34)
      at <unknown> (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/task.js:28:23)
    From previous event:
      at Object.run (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/task.js:22:10)
      at backendRequest (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/socket.js:425:28)
    From previous event:
      at Socket.<anonymous> (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/server/lib/socket.js:433:28)
      at Socket.emit (events.js:315:21)
      at <unknown> (/Users/andieromero/Library/Caches/Cypress/6.4.0/Cypress.app/Contents/Resources/app/packages/socket/node_modules/socket.io/lib/socket.js:528:13)
      at processTicksAndRejections (internal/process/task_queues.js:79:12)```

TimeoutError: waiting for selector "#signIn" failed: timeout 30000ms exceeded

I am getting this error only when I use headless: true in the config. I downloaded this repo and debugged the problem locally, and what happens is that the Login button has a different id.

I took a screenshot and wrote the HTML of the login page when the exception occurred.

screenshot

Part of popup page html code

...
<div jscontroller="VfPfjf" jsname="vZSTIf">
  <label class="XJ6xof" for="password">Introduce tu contraseña</label>
  <input id="password" name="Passwd" type="password" spellcheck="false" autofocus="" class="mCAa0e" jsname="pRFXed" />
</div>
<input type="submit" class="MK9CEd MVpUfe" jsname="M2UYVd" jscontroller="rrJN5c" jsaction="aJAbCd:zbvklb" value="Iniciar sesión" id="submit" />
...

The Login button has a #submit as id, but the plugin search #signIn.

let buttonSelector = options.headless ? '#signIn' : '#passwordNext'

My configuration:

const socialLoginOptions = {
  username,
  password,
  loginUrl,
  headless: true,
  logs: false,
  getAllBrowserCookies: true,
  loginSelector: 'button[id="googleBtn"]',
  postLoginSelector: '.photo-profile',
  loginSelectorDelay: 500,
  popupDelay: 1000,
  isPopup: true
}

Suggestion
Change selector by first input[type='submit] coincidence, works for me.

"Couldn't sign you in" This browser or app may not be secured, Google login fails

Hello,

I am trying to perform Google Auth operation and using the same example code provided in README file.

Initially with option headless: true it took long and returns following error:

image

Then I tried to debug the situation and toggled headless: false then situation was different.

image

I have recorded small video of this: https://www.loom.com/share/0b13b447f9c54a4e81beeee856e45544

Setup code:

it('Login through Google', () => {
    const username = Cypress.env('LOGIN_EMAIL');
    const password = Cypress.env('PASSWORD');
    const loginUrl = Cypress.env('LOGIN_URL');
    const cookieName = Cypress.env('COOKIE_NAME');
    const socialLoginOptions = {
      username,
      password,
      loginUrl,
      headless: true, // for debugging I used false option 
      isPopup: true,
      logs: false,
      loginSelector: 'button',
      popupDelay: 3000,
    };

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({ cookies }) => {
      cy.clearCookies();

      const cookie = cookies.filter((cookie) => cookie.name === cookieName).pop();
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure,
        });

        Cypress.Cookies.defaults({
          whitelist: cookieName,
        });
      }
    });
  });
{
  "chromeWebSecurity": true // I have tried all 3 options (removing this property, true and false
}

Not sure if I am doing something wrong or it requires extra configurations. Kindly help!

passwordSubmitBtn is not working with customized login

so i have been trying to login with apple using customLogin but i have not been able to use the passwordSubmitBtn as apple login form has to be username then the password so as i have usernameSubmitBtn working and it passes on to the password then i cant seem to press the passwordSubmitBtn as both of the cases have same btn id where one works with username and does not with password is very wierd 'button[id*="sign-in"]' is just skips looking for passwordBtn and moves toward looking for post login selector.

My specs.js is as follows :

export const options = {
usernameField: null,
passwordField: null,
username: null,
password: null,
loginUrl: Cypress.env("loginUrl"),
headless: false,
logs: true,
loginSelector: null,
isPopup: true,
popupDelay: 5000,
loginSelectorDelay: 1000,
usernameSubmitBtn: null,
passwordSubmitBtn: null,
postLoginSelector : 'button[id*="sign-in"]',
postLoginClick : 'button[id*="sign-in"]',
moreStep : 'pin'
};

describe("Login", () => {
it("Custom Login", () => {        
  options.postLoginClick = PASSWORD_SELECTOR
  options.loginSelector = 'form[action*="apple"]'
  options.usernameField = EMAIL_SELECTOR
  options.passwordField = PASSWORD_SELECTOR
  options.passwordSubmitBtn = 'button[id*="sign-in"]'
  options.usernameSubmitBtn = 'button[id*="sign-in"]';

    
  const cookieName = Cypress.env("cookieName");
  return cy
    .task("customizedLogin",aoptions)
    .then(({ cookies }) => {
      cy.clearCookies();

      const cookie = cookies
        .filter(cookie => cookie.name === cookieName)
        .pop();
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        });
        Cypress.Cookies.defaults({
          whitelist: cookieName
        });
      }
    });
});
});

my index.js is as follows :

const {GoogleSocialLogin,FacebookSocialLogin,CustomizedLogin} = require('cypress-social-logins').plugins

module.exports = (on, config) => {
    on('task', {
      GoogleSocialLogin: GoogleSocialLogin,
      FacebookSocialLogin: FacebookSocialLogin,
      customizedLogin : (options) => {
        return CustomizedLogin(options)
    },
     
  }
)
}

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two-Factor Authentication, make configure the auth-only level is supported. semantic-release cannot publish with the default auth-and-writes level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot 📦🚀

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.