cyrus-and / chrome-har-capturer

Capture HAR files from a Chrome instance
MIT License
530 stars 90 forks source link

First press button and then create HAR [Question] #75

Closed hubitor closed 5 years ago

hubitor commented 5 years ago

As the title suggests: Is it possible to first press a button and then create a HAR with CHC? or to put it in another way: is it possible to combine puppeteer with CHC and how?

cyrus-and commented 5 years ago

You can generate the event log with Puppeteer then pass it to CHC via the fromLog method.

hubitor commented 5 years ago

Thanks. I've tried to create a simple example but I couldn't make it work. This is what I have done so far:

const CHC = require("chrome-har-capturer");
const puppeteer = require("puppeteer");
const { promisify } = require("util");

const events = [];

const watchedEvents = [
  "Network.dataReceived",
  "Network.loadingFailed",
  "Network.loadingFinished",
  "Network.requestWillBeSent",
  "Network.resourceChangedPriority",
  "Network.responseReceived",
  "Page.domContentEventFired",
  "Page.loadEventFired"
];

const url = "https://example.com";

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();

  const client = await page.target().createCDPSession();
  await client.send("Page.enable");
  await client.send("Network.enable");

  watchedEvents.forEach(method => {
    client.on(method, params => {
      events.push({ method, params });
    });
  });

  await page.goto(url);

  try {
   let  har = await CHC.fromLog(url, events, { content: true });
  await promisify(fs.writeFile)("out.har", JSON.stringify(har));
  } catch (e) {
    console.warn(e.message);
  }

  //await browser.close();
  console.log(events);
})();

but I'm getting this:

Incomplete event log
[ { method: 'Network.requestWillBeSent',
    params:
     { requestId: '6F472669FCAA2EDF428A875B329F4874',
       loaderId: '6F472669FCAA2EDF428A875B329F4874',
       documentURL: 'https://example.com/',
       request: [Object],
       timestamp: 90419.142093,
       wallTime: 1568586127.407924,
       initiator: [Object],
       type: 'Document',
       frameId: '6CAC968504C8589C4BF0BB4D543D1F60',
       hasUserGesture: false } },
  { method: 'Network.responseReceived',
    params:
     { requestId: '6F472669FCAA2EDF428A875B329F4874',
       loaderId: '6F472669FCAA2EDF428A875B329F4874',
       timestamp: 90419.548924,
       type: 'Document',
       response: [Object],
       frameId: '6CAC968504C8589C4BF0BB4D543D1F60' } },
  { method: 'Network.dataReceived',
    params:
     { requestId: '6F472669FCAA2EDF428A875B329F4874',
       timestamp: 90419.561069,
       dataLength: 1270,
       encodedDataLength: 0 } },
  { method: 'Network.loadingFinished',
    params:
     { requestId: '6F472669FCAA2EDF428A875B329F4874',
       timestamp: 90419.547329,
       encodedDataLength: 805,
       shouldReportCorbBlocking: false } },
  { method: 'Page.loadEventFired',
    params: { timestamp: 90419.562866 } },
  { method: 'Page.domContentEventFired',
    params: { timestamp: 90419.613617 } } ]

and no HAR is being generated. Why I'm getting "Incomplete event log"?

cyrus-and commented 5 years ago

That's because you're requesting the content (content: true) but not providing synthetic Network.getResponseBody events as requested by the fromLog documentation.

cyrus-and commented 5 years ago

Try the following, you'll still have to handle errors and maybe some corner case but this is the gist of it:

const CHC = require('chrome-har-capturer');
const puppeteer = require('puppeteer');
const fs = require('fs');
const { promisify } = require('util');

const events = [];

const watchedEvents = [
    'Network.dataReceived',
    'Network.loadingFailed',
    'Network.loadingFinished',
    'Network.requestWillBeSent',
    'Network.resourceChangedPriority',
    'Network.responseReceived',
    'Page.domContentEventFired',
    'Page.loadEventFired'
];

const url = 'https://example.com';

let counter = 0;

async function finish() {
    try {
        const har = await CHC.fromLog(url, events, { content: true });
        await promisify(fs.writeFile)('out.har', JSON.stringify(har));
    } catch (e) {
        console.warn(e.message);
    }
}

(async () => {
    const browser = await puppeteer.launch({
        headless: false,
        args: [
            '--no-sandbox',
            '--disable-setuid-sandbox'
        ]
    });

    const page = await browser.newPage();

    const client = await page.target().createCDPSession();
    await client.send('Page.enable');
    await client.send('Network.enable');

    watchedEvents.forEach(method => {
        client.on(method, params => {
            events.push({method, params});
        });
    });

    client.on('Network.loadingFinished', async ({requestId}) => {
        // call Network.getResponsebody manually for each
        // Network.loadingFinished events
        counter++;
        const params = await client.send('Network.getResponseBody', {requestId});
        counter--;

        // build the synthetic events
        const {body, base64Encoded} = params;
        events.push({
            method: 'Network.getResponseBody',
            params: {
                requestId,
                body,
                base64Encoded
            }
        });

        // when there are no more pending invocations build the HAR
        if (counter === 0) {
            finish();
        }
    });

    await page.goto(url);
})();
cyrus-and commented 5 years ago

OK, I clarified that section of the README, feel free to comment if you need further assistance.

hubitor commented 5 years ago

I tried it and it looks good. Thanks!