Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'C:\Users\****\Documents\Project\MI\PuppetScript\RetestStart\node_modules\lighthouse\lighthouse-core\fraggle-rock\api.js' #14791

Closed
2 tasks done
Darshan20231402 opened this issue Feb 14, 2023 · 9 comments

Comments

@Darshan20231402
Copy link

FAQ

URL

https://www.mi-perftest1.com/default.mi?phoenix=true

What happened?

I am trying to automate the lighthouse User flow `by integrating with Puppeteer script by referring to the article [https://web.dev/lighthouse-user-flows/]
I have performed all the required steps but still I am facing ERR_MODULE_NOT_FOUND error. Appreciate your help on this issue.

Error log:
node:internal/errors:490
ErrorCaptureStackTrace(err);
^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'C:\Users\dards\Documents\Project\MI\PuppetScript\RetestStart\node_modules\lighthouse\lighthouse-core\fraggle-rock\api.js' imported from C:\Users\dards\Documents\Project\MI\PuppetScript\RetestStart\index.js
at new NodeError (node:internal/errors:399:5)
at finalizeResolution (node:internal/modules/esm/resolve:326:11)
at moduleResolve (node:internal/modules/esm/resolve:945:10)
at defaultResolve (node:internal/modules/esm/resolve:1153:11)
at nextResolve (node:internal/modules/esm/loader:163:28)
at ESMLoader.resolve (node:internal/modules/esm/loader:838:30)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
at ModuleWrap. (node:internal/modules/esm/module_job:77:40)
at link (node:internal/modules/esm/module_job:76:36) {
code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v18.14.0

What did you expect?

Need your help to fix the above explained issue.

What have you tried?

I am facing this issue with the lighthouse package

How were you running Lighthouse?

node

Lighthouse Version

10.0.0

Chrome Version

110.0.5481.77

Node Version

v18.14.0

OS

Windows

Relevant log output

PS C:\Users\dards\Documents\Project\MI\PuppetScript\RetestStart> node index.js
node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'C:\Users\dards\Documents\Project\MI\PuppetScript\RetestStart\node_modules\lighthouse\lighthouse-core\fraggle-rock\api.js' imported from C:\Users\dards\Documents\Project\MI\PuppetScript\RetestStart\index.js
    at new NodeError (node:internal/errors:399:5)
    at finalizeResolution (node:internal/modules/esm/resolve:326:11)
    at moduleResolve (node:internal/modules/esm/resolve:945:10)
    at defaultResolve (node:internal/modules/esm/resolve:1153:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:838:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v18.14.0
@connorjclark
Copy link
Collaborator

connorjclark commented Feb 14, 2023

Can you provide the script you are using?

Oh, https://web.dev/lighthouse-user-flows/ is referring to pre-10.0 stuff. We need to update that.

@connorjclark connorjclark added docs and removed bug labels Feb 14, 2023
@Darshan20231402
Copy link
Author

Darshan20231402 commented Feb 15, 2023 via email

@adamraine
Copy link
Member

@Darshan20231402 the location of the user flow api has changed in Lighthouse 10.0. To import it you can use import {startFlow} from 'lighthouse'

@Darshan20231402
Copy link
Author

Darshan20231402 commented Feb 16, 2023

Thank you @adamraine , I am able to startFlow by importing as suggested.
But now the problem is due to startTimespan it starts the mobile emulation instead of desktop screen even after setting Viewport. As per the discussion #13320 , we need to import /desktop-config.js but with 10 version we do not have /lighthouse-core/ folder. Can you please suggest alternative. I tried import desktopConfig but still browser loads as mobile screen.

const config = require('lighthouse/lighthouse-core/config/desktop-config.js');
const flow = await startFlow(page, {config, name: 'open google'});

#13320

@connorjclark
Copy link
Collaborator

Change lighthouse-core to core.

Please look over the breaking changes section of 10.0 before continuing: https://github.com/GoogleChrome/lighthouse/releases/tag/v10.0.0

@adamraine
Copy link
Member

You can also do import {desktopConfig} from 'lighthouse'

@Darshan20231402
Copy link
Author

Darshan20231402 commented Feb 17, 2023

Thank you @connorjclark and @adamraine .
The problem I am stating here is, as soon I make a startTimespan(); call the browser changes from Desktop to mobile emulation but I want to continue with Desktop browser screen size.

Please find the script below:----
// const puppeteer = require('puppeteer'); // v13.0.0 or later
// const fs = require("fs");
import {writeFileSync} from 'fs';
import puppeteer from 'puppeteer';
// import * as pptrTestingLibrary from 'pptr-testing-library';
import lighthouse, { desktopConfig, startTimespan } from 'lighthouse';
import {startFlow} from 'lighthouse';

// const {getDocument, queries} = pptrTestingLibrary;

(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
const timeout = 55000;
const flow = await startFlow(page);
page.setDefaultTimeout(timeout);
{
const targetPage = page;
await targetPage.setViewport({
width: 1263,
height: 556
})
}
{
const timeout = 25000;
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await flow.startTimespan();
await targetPage.goto('URL');
await Promise.all(promises);
await flow.endTimespan();
await flow.snapshot();
}
{
const targetPage = page;
await targetPage.keyboard.up('Tab');
}
{
const targetPage = page;
await targetPage.keyboard.up('Shift');
}
// {
// const targetPage = page;
// const promises = [];
// promises.push(targetPage.waitForNavigation());
// await targetPage.goto('URL');
// await Promise.all(promises);
// }
{
const targetPage = page;
await scrollIntoViewIfNeeded([
[
'aria/DESTINATION WHERE CAN WE TAKE YOU?SEARCH FROM CURRENT LOCATION BELOW.'
],
[
'#\31 676200461561-search-destination'
],
[
'xpath///[@id="1676200461561-search-destination"]'
],
[
'pierce/#\31 676200461561-search-destination'
]
], targetPage, timeout);
const element = await waitForSelectors([
[
'aria/DESTINATION WHERE CAN WE TAKE YOU?SEARCH FROM CURRENT LOCATION BELOW.'
],
[
'#\31 676200461561-search-destination'
],
[
'xpath///
[@id="1676200461561-search-destination"]'
],
[
'pierce/#\31 676200461561-search-destination'
]
], targetPage, { timeout, visible: true });
await element.click({
offset: {
x: 134.3333282470703,
y: 3.3333282470703125,
},
});
}
{
const targetPage = page;
await scrollIntoViewIfNeeded([
[
'aria/DESTINATION WHERE CAN WE TAKE YOU?SEARCH FROM CURRENT LOCATION BELOW.'
],
[
'#\31 676200467634-search-destination'
],
[
'xpath///[@id="1676200467634-search-destination"]'
],
[
'pierce/#\31 676200467634-search-destination'
]
], targetPage, timeout);
const element = await waitForSelectors([
[
'aria/DESTINATION WHERE CAN WE TAKE YOU?SEARCH FROM CURRENT LOCATION BELOW.'
],
[
'#\31 676200467634-search-destination'
],
[
'xpath///
[@id="1676200467634-search-destination"]'
],
[
'pierce/#\31 676200467634-search-destination'
]
], targetPage, { timeout, visible: true });
const inputType = await element.evaluate(el => el.type);
if (inputType === 'select-one') {
await changeSelectElement(element, 'Chicago')
} else if ([
'textarea',
'text',
'url',
'tel',
'search',
'password',
'number',
'email'
].includes(inputType)) {
await typeIntoElement(element, 'Chicago');
} else {
await changeElementValue(element, 'Chicago');
}
}
{
const timeout = 25000;
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await scrollIntoViewIfNeeded([
[
'aria/Find Hotels'
],
[
'#\31 676200467634-find-a-hotel-homePage-form > button'
],
[
'xpath///[@id="1676200467634-find-a-hotel-homePage-form"]/button'
],
[
'pierce/#\31 676200467634-find-a-hotel-homePage-form > button'
],
[
'text/Find Hotels'
]
], targetPage, timeout);
const element = await waitForSelectors([
[
'aria/Find Hotels'
],
[
'#\31 676200467634-find-a-hotel-homePage-form > button'
],
[
'xpath///
[@id="1676200467634-find-a-hotel-homePage-form"]/button'
],
[
'pierce/#\31 676200467634-find-a-hotel-homePage-form > button'
],
[
'text/Find Hotels'
]
], targetPage, { timeout, visible: true });
// await flow.startTimespan();
// await targetPage.setViewport({ "width": 1263, "height": 556 });
await element.click({
offset: {
x: 132.33331298828125,
y: 38.33332824707031,
},
});
await Promise.all(promises);
// await flow.endTimespan();
await flow.snapshot();
}
{
const targetPage = page;
await targetPage.keyboard.down('Alt');
}
writeFileSync('reportTime.html', await flow.generateReport());
await browser.close();
//User flow ends here
async function waitForSelectors(selectors, frame, options) {
for (const selector of selectors) {
try {
return await waitForSelector(selector, frame, options);
} catch (err) {
console.error(err);
}
}
throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors));
}

async function scrollIntoViewIfNeeded(selectors, frame, timeout) {
  const element = await waitForSelectors(selectors, frame, { visible: false, timeout });
  if (!element) {
    throw new Error(
      'The element could not be found.'
    );
  }
  await waitForConnected(element, timeout);
  const isInViewport = await element.isIntersectingViewport({threshold: 0});
  if (isInViewport) {
    return;
  }
  await element.evaluate(element => {
    element.scrollIntoView({
      block: 'center',
      inline: 'center',
      behavior: 'auto',
    });
  });
  await waitForInViewport(element, timeout);
}

async function waitForConnected(element, timeout) {
  await waitForFunction(async () => {
    return await element.getProperty('isConnected');
  }, timeout);
}

async function waitForInViewport(element, timeout) {
  await waitForFunction(async () => {
    return await element.isIntersectingViewport({threshold: 0});
  }, timeout);
}

async function waitForSelector(selector, frame, options) {
  if (!Array.isArray(selector)) {
    selector = [selector];
  }
  if (!selector.length) {
    throw new Error('Empty selector provided to waitForSelector');
  }
  let element = null;
  for (let i = 0; i < selector.length; i++) {
    const part = selector[i];
    if (element) {
      element = await element.waitForSelector(part, options);
    } else {
      element = await frame.waitForSelector(part, options);
    }
    if (!element) {
      throw new Error('Could not find element: ' + selector.join('>>'));
    }
    if (i < selector.length - 1) {
      element = (await element.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
    }
  }
  if (!element) {
    throw new Error('Could not find element: ' + selector.join('|'));
  }
  return element;
}

async function waitForElement(step, frame, timeout) {
  const {
    count = 1,
    operator = '>=',
    visible = true,
    properties,
    attributes,
  } = step;
  const compFn = {
    '==': (a, b) => a === b,
    '>=': (a, b) => a >= b,
    '<=': (a, b) => a <= b,
  }[operator];
  await waitForFunction(async () => {
    const elements = await querySelectorsAll(step.selectors, frame);
    let result = compFn(elements.length, count);
    const elementsHandle = await frame.evaluateHandle((...elements) => {
      return elements;
    }, ...elements);
    await Promise.all(elements.map((element) => element.dispose()));
    if (result && (properties || attributes)) {
      result = await elementsHandle.evaluate(
        (elements, properties, attributes) => {
          for (const element of elements) {
            if (attributes) {
              for (const [name, value] of Object.entries(attributes)) {
                if (element.getAttribute(name) !== value) {
                  return false;
                }
              }
            }
            if (properties) {
              if (!isDeepMatch(properties, element)) {
                return false;
              }
            }
          }
          return true;

          function isDeepMatch(a, b) {
            if (a === b) {
              return true;
            }
            if ((a && !b) || (!a && b)) {
              return false;
            }
            if (!(a instanceof Object) || !(b instanceof Object)) {
              return false;
            }
            for (const [key, value] of Object.entries(a)) {
              if (!isDeepMatch(value, b[key])) {
                return false;
              }
            }
            return true;
          }
        },
        properties,
        attributes
      );
    }
    await elementsHandle.dispose();
    return result === visible;
  }, timeout);
}

async function querySelectorsAll(selectors, frame) {
  for (const selector of selectors) {
    const result = await querySelectorAll(selector, frame);
    if (result.length) {
      return result;
    }
  }
  return [];
}

async function querySelectorAll(selector, frame) {
  if (!Array.isArray(selector)) {
    selector = [selector];
  }
  if (!selector.length) {
    throw new Error('Empty selector provided to querySelectorAll');
  }
  let elements = [];
  for (let i = 0; i < selector.length; i++) {
    const part = selector[i];
    if (i === 0) {
      elements = await frame.$$(part);
    } else {
      const tmpElements = elements;
      elements = [];
      for (const el of tmpElements) {
        elements.push(...(await el.$$(part)));
      }
    }
    if (elements.length === 0) {
      return [];
    }
    if (i < selector.length - 1) {
      const tmpElements = [];
      for (const el of elements) {
        const newEl = (await el.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
        if (newEl) {
          tmpElements.push(newEl);
        }
      }
      elements = tmpElements;
    }
  }
  return elements;
}

async function waitForFunction(fn, timeout) {
  let isActive = true;
  const timeoutId = setTimeout(() => {
    isActive = false;
  }, timeout);
  while (isActive) {
    const result = await fn();
    if (result) {
      clearTimeout(timeoutId);
      return;
    }
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  throw new Error('Timed out');
}

async function changeSelectElement(element, value) {
  await element.select(value);
  await element.evaluateHandle((e) => {
    e.blur();
    e.focus();
  });
}

async function changeElementValue(element, value) {
  await element.focus();
  await element.evaluate((input, value) => {
    input.value = value;
    input.dispatchEvent(new Event('input', { bubbles: true }));
    input.dispatchEvent(new Event('change', { bubbles: true }));
  }, value);
}

async function typeIntoElement(element, value) {
  const textToType = await element.evaluate((input, newValue) => {
    if (
      newValue.length <= input.value.length ||
      !newValue.startsWith(input.value)
    ) {
      input.value = '';
      return newValue;
    }
    const originalValue = input.value;
    input.value = '';
    input.value = originalValue;
    return newValue.substring(originalValue.length);
  }, value);
  await element.type(textToType);
}

})().catch(err => {
console.error(err);
process.exit(1);
});

@adamraine
Copy link
Member

It sounds like you want to test a desktop page but inherit whatever viewport changes you made with Puppeteer. The way you would set that up is:

import {startFlow, desktopConfig} from 'lighthouse';

const flow = await startFlow(page, {
  config: desktopConfig,
  flags: {screenEmulation: {disabled: true}}
});
await page.setViewport({
  width: 1263,
  height: 556
});

@Darshan20231402
Copy link
Author

Darshan20231402 commented Feb 20, 2023

Thank you @adamraine . It worked as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants