【问题标题】:Puppeteer to invoke javascript function from an external .js filePuppeteer 从外部 .js 文件调用 javascript 函数
【发布时间】:2021-05-19 04:15:24
【问题描述】:

任何人都知道如何从 puppeteer调用 javascript 函数,该函数不是内联的,而是在 external .js 文件中的。如果它在 html->head->script 标记中内联,则它可以工作,但如果脚本标记指向外部 .js 文件,则不会

示例 HTML 文件

<html>
    <head>
        <script type="text/javascript">
            function inlineFunction()  {
                window.location.replace('https://www.geeksforgeeks.org');
            }
        </script>
        <script src="script.js" type="text/javascript">
        </script>
    </head>
    <body>
        <p>Hello</p>
        <p>this is an online html</p>
        <p>Link with tag a <a href="https://www.geeksforgeeks.org" name="arivalink">Href Link</a></p>
        <p>Link with inline java script - <a href="#" onClick='inlineFunction();'>Inline JS link</a></p><!-- Works -->
        <p>Link with external JS file w/o tagname - <a href="#" onClick='fileFunction();'>Ext JS Link</a></p><!-- Does not work -->
        <p>Link with external JS file w/ tagname - <a href="#" onClick='fileFunction();' name="geeksLink">Ext JS Link</a></p><!-- Does not work -->
    </body>
</html>

示例 Javascript 文件

/*----------------------------------------------------*/
/* External Javascript File                           */
/*----------------------------------------------------*/

function fileFunction() {

    window.location.replace('https://www.geeksforgeeks.org');

}

Puppeteer 代码示例

const puppeteer = require('puppeteer');

async function start() {
    const browser = await puppeteer.launch({
        headless: false
    });

    const page = await browser.newPage();

    //Change the path of "url" to your local path for the html file
    const url = 'file:///Users/sam.gajjar/SG/Projects/headless-chrome/sample.html'; 
    var link = '[name="link"]';

    console.log("Main URL Called");
    await page.goto(url);

    console.log("Link via HTML tag A called");
    await page.click(link);

    await page.waitForTimeout(5000) // Wait 5 seconds
        .then(() => page.goBack());
    
    console.log("Callng inline JS Function");
    await page.evaluate(() => inlineFunction());

    await page.waitForTimeout(5000) // Wait 5 seconds
        .then(() => page.goBack());

    console.log("Callng extjs file Function");
    await page.evaluate(() => fileFunction());

    await page.waitForTimeout(5000) // Wait 5 seconds
        .then(() => page.goBack());

    // console.log("Callng extjs file Function w/tag name");
    // const element = await page.$$('[a href="#"]');

    // await page.waitForTimeout(5000)
        // .then(() => page.goBack());
}

start();

【问题讨论】:

  • @ggorlen - 添加了 puppeteer 代码,其中调用 HTML A 标记和调用内联 JS 函数有效,但外部 JS 中的相同函数不起作用。 puppeteer 代码将使用 HTML A 调用 Link 然后返回并调用内联 JS 函数,然后返回并在失败的地方调用 extJS 函数。

标签: javascript puppeteer


【解决方案1】:

首先,[name="link"] 应该是 [name="arivalink"] 以匹配您的 DOM。我认为这是一个错字。

另外,我建议使用Promise.all navigation pattern 而不是waitForTimeout,这可能会导致竞争条件(尽管这似乎与本例中的问题无关)。

至于主要问题,外部文件运行良好,所以这是一个红鲱鱼。您可以通过在导航到 sample.html 后立即调用 page.evaluate(() =&gt; fileFunction()) 来证明这一点。

真正的问题是,当您使用window.location.replace('https://www.geeksforgeeks.org'); 导航时,Chromium 不会将该操作推送到历史堆栈中。它替换当前的 URL,所以page.goBack() 回到原来的about:blank 而不是你所期望的sample.htmlabout:blank 里面没有 fileFunction,所以 Puppeteer 抛出。

现在,当您使用 Puppeteer 单击 [name="link"] 时,确实推送历史堆栈,因此 goBack 工作正常。

您可以通过在浏览器中加载 sample.html 并在没有 Puppeteer 的情况下手动导航来重现此行为。

长话短说,如果您在浏览器上下文中使用运行window.location.replaceevaluate 调用函数,则不能依赖page.goBack。您需要使用page.goto 才能返回sample.html

有一个有趣的细微差别:如果您使用 page.click 调用运行 location.replace("...") 的 JS,Puppeteer 推送历史堆栈,page.goBack 将按预期运行。如果您使用 page.evaluate(() =&gt; location.replace("...")); 调用相同的 JS 逻辑,Puppeteer 不会将当前 URL 推送到历史堆栈,page.goBack 将无法按预期工作。 evaluate 行为更符合“手动”浏览(即作为人类在 GUI 上使用鼠标和键盘)。

下面是演示所有这些的代码。一切都在同一个目录中,node index.js 运行 Puppeteer(我使用 Puppeteer 9.0.0)。

script.js

const replaceLocation = () => location.replace("https://www.example.com");
const setLocation = () => location = "https://www.example.com";

sample.html

<!DOCTYPE html>
<html lang="en">
<head>
  <title>sample</title>
</head>
<body>
  <div>
    <a href="https://www.example.com">normal link</a> | 
    <a href="#" onclick="replaceLocation()">location.replace()</a> | 
    <a href="#" onclick="setLocation()">location = ...</a>
  </div>
  <script src="script.js"></script>
</body>
</html>

index.js

const puppeteer = require("puppeteer");

const url = "file:///Users/sam.gajjar/SG/Projects/headless-chrome/sample.html";
const log = (() => {
  let logId = 0;
  return (...args) => console.log(logId++, ...args);
})();
let browser;

(async () => {
  browser = await puppeteer.launch({
    headless: false, 
    slowMo: 500,
  });
  const [page] = await browser.pages();
  await page.goto(url);

  // display the starting location
  log(page.url()); // 0 sample.html
  
  // click the normal link and pop the browser stack with goBack
  await Promise.all([
    page.waitForNavigation(),
    page.click("a:nth-child(1)"),
  ]);
  log(page.url()); // 1 example.com
  await page.goBack();
  log(page.url()); // 2 sample.html
  
  // fire location.replace with click
  await Promise.all([
    page.waitForNavigation(),
    page.click("a:nth-child(2)"), // pushes history (!)
  ]);
  log(page.url()); // 3 example.com
  await page.goBack();
  log(page.url()); // 4 sample.html

  // fire location.replace with evaluate
  await Promise.all([
    page.waitForNavigation(),
    page.evaluate(() => replaceLocation()), // doesn't push history
  ]);
  log(page.url()); // 5 example.com
  await page.goBack();
  log(page.url()); // 6 about:blank <--- here's your bug!
  
  await page.goto(url); // go to sample.html from about:blank <-- here's the fix
  log(page.url()); // 7 sample.html
  
  // use location = and see that goBack takes us to sample.html
  await Promise.all([
    page.waitForNavigation(),
    page.evaluate(() => setLocation()), // same behavior as page.click
  ]);
  log(page.url()); // 8 example.com
  await page.goBack();
  log(page.url()); // 9 sample.html
})()
  .catch(err => console.error(err))
  .finally(async () => await browser.close())
;

【讨论】:

  • 所以这很有效,非常感谢。但是,我通过创建 sample.html 和外部 JS 文件来简化了实际情况。但这不适用于实际情况。我正在使用一个有 20 年历史的电子商务网站,这是我在页面中拥有的实际链接。现在问题..是否有多个外部 JS 并且此链接位于嵌套框架集中,即 6 级深框架? 购物篮
  • 什么不完全有效?如果您使用完整的失败代码打开一个新问题,我可以尝试提供帮助。做一个最小的例子很好,但是如果这个例子有污染/混乱的行为,比如location.replace borking goBack 逻辑,这不是你最初意图的一部分,那么这个例子就离你原来的场景太远了。跨度>
  • 再次,请打开一个新问题并包含所有相关详细信息。所有这些细节(如 6 层框架)都是重要信息,与您在此处显示的根本不同。
  • 是的,我同意,但在实际场景中我不需要使用 goBack() 它只是在所有链接都在 sample.html 中并想要显示行为的简化场景中使用 JS 的链接变化
  • 这很好,但事实证明goBack/location.replace 的东西无意中产生了一堆新的(但有趣的)问题,这些问题与你现在描述的问题无关。我认为这是您的真实用例。
猜你喜欢
  • 1970-01-01
  • 2019-01-23
  • 1970-01-01
  • 2017-12-02
  • 1970-01-01
  • 2020-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多