Blue
Blue

Reputation: 251

Clicking a selector with Puppeteer

So I am having trouble clicking a login button on the nike website..

I am not sure why It keeps crashing, well because it can't find the selector I guess but I am not sure what I am doing wrong.

I would like to also say I am having some sort of memory leak before puppeteer crashes and sometimes it will even crash my macbook completely if I don't cancel the process in time inside the console.

EDIT: This code also causes a memory leak whenever it crashes forcing me to have to hard reset my mac if I don't cancel the application fast enough.

Node Version: 14.4.0 Puppeteer Version: 5.2.1

Current code:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: false,
        defaultViewport: null,
        args: ['--start-maximized']
    })

    const page = await browser.newPage()
    await page.goto('https://www.nike.com/')

    const winner = await Promise.race([
        page.waitForSelector('[data-path="join or login"]'),
        page.waitForSelector('[data-path="sign in"]')
    ])

    await page.click(winner._remoteObject.description)
})()

I have also tried:

await page.click('button[data-var]="loginBtn"');

Upvotes: 6

Views: 30340

Answers (3)

John Doherty
John Doherty

Reputation: 4085

You need to use { waitUntil: 'networkidle0' } with page.goto

This tells puppeteer to wait for the network to be idle (no requests for 500ms)

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: false,
        defaultViewport: null,
        args: ['--start-maximized']
    })

    const page = await browser.newPage()

    // load the nike.com page and wait for it to fully load (inc A/B scripts)
    await page.goto('https://www.nike.com/', { waitUntil: 'networkidle0' })

    // select whichever element appears first
    var el = await page.waitForSelector('[data-path="join or login"], [data-path="sign in"]', { timeout: 1000 })

    // execute click
    await page.click(el._remoteObject.description)
})()

Upvotes: 2

theDavidBarton
theDavidBarton

Reputation: 8841

They are A/B testing their website, so you may land on a page with very different selectors than you retreived while you visited the site from your own chrome browser.

In such cases you can try to grab the elements by their text content (unfortunately in this specific case that also changes over the designs) using XPath and its contains method. E.g. $x('//span[contains(text(), "Sign In")]')[0]

So I suggest to detect both button versions and get their most stable selectors, these can be based on data attributes as well:

A

$('[data-path="sign in"]')

B

$('[data-path="join or login"]')

Then with a Promise.race you can detect which button is present and then extract its selector from the JSHandle@node like this: ._remoteObject.description:

{
  type: 'object',
  subtype: 'node',
  className: 'HTMLButtonElement',
  description: 'button.nav-btn.p0-sm.body-3.u-bold.ml2-sm.mr2-sm',
  objectId: '{"injectedScriptId":3,"id":1}'
}

=>

button.nav-btn.p0-sm.prl3-sm.pt2-sm.pb2-sm.fs12-nav-sm.d-sm-b.nav-color-grey.hover-color-black

Example:

const browser = await puppeteer.launch({
  headless: false,
  defaultViewport: null,
  args: ['--start-maximized']
})
const page = await browser.newPage()
await page.goto('https://www.nike.com/')
const winner = await Promise.race([
  page.waitForSelector('[data-path="join or login"]'),
  page.waitForSelector('[data-path="sign in"]')
])

await page.click(winner._remoteObject.description)

FYI: Maximize the browser window as well to make sure the elment has the same selector name.

defaultViewport: null, args: ['--start-maximized']

Chromium starts in a bit smaller window with puppeteer by default.

Upvotes: 3

Vladyslav Frizen
Vladyslav Frizen

Reputation: 227

Try it:

await page.click('button[data-var="loginBtn"]');

Upvotes: 7

Related Questions