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

Support a way to listen for javascript events #184

Closed
MTRNord opened this issue Mar 16, 2018 · 16 comments
Closed

Support a way to listen for javascript events #184

MTRNord opened this issue Mar 16, 2018 · 16 comments

Comments

@MTRNord
Copy link

MTRNord commented Mar 16, 2018

Hi I have a event I listen to in JS via the following Code. How can I listen to the event via chromedp?

window.addHook('mapDataRefreshEnd', function (data) {
    console.log("Done!")
});
@MTRNord
Copy link
Author

MTRNord commented Mar 16, 2018

To be more exact: I want to wait until the event fired

@MTRNord
Copy link
Author

MTRNord commented Mar 16, 2018

also window.addHook gets oaded dynamicly but works like addEventListener

@mvdan
Copy link
Contributor

mvdan commented May 6, 2019

Please provide a small HTML file with a small chromedp Go program to reproduce this issue.

@montanaflynn
Copy link

I'm also trying to listen and wait for events, specifically dom ready and window loaded.

window.addEventListener('load', function() {
    console.log('All assets are loaded')
})
document.addEventListener("DOMContentLoaded", function() {
  console.log("done")
});

Maybe it's more of a request for help or new feature request than an issue. It's probably possible to use the Evaluate method using callbacks but I'm unsure how to do so.

Something like this would be nice:

chromedp.WaitEvent('window.load', func(event chromedp.Event) {
	fmt.Printf("%+v\n", event)
	return nil
}),

chromedp.WaitEvent('document.DOMContentLoaded', func(event chromedp.Event) {
	fmt.Printf("%+v\n", event)
	return nil
}),

@mvdan
Copy link
Contributor

mvdan commented May 8, 2019

@montanaflynn what you probably want to do is listen/wait for the devtools events instead. See https://godoc.org/github.com/chromedp/chromedp#example-ListenTarget--ConsoleLog, which you can use to listen for events like https://chromedevtools.github.io/devtools-protocol/tot/Page#event-loadEventFired.

I guess a way to receive JS events would be useful, but it's not clear to me in what cases it would be useful. Happy to take a look if anyone provides a full example, or a way to make it work with https://chromedevtools.github.io/devtools-protocol/tot/.

@MTRNord
Copy link
Author

MTRNord commented May 8, 2019

So this issue is really old and I am not activly working on the project anymore I had this issue with. I only roughly remember that I was working on a screenshot chat bot for https://intel.ingress.com/intel

This is an google map which was heavily modified. The only way to know for sure that the map fully loaded at the position I looked at is to listen for the specific javascript events fired by the map. So thoose events I had are fired exactly by that map code (cant provide source as obviously that one is copyrighted by google and obfuscated anyway). But in general this issue basicly is the same for any regular javascript event no matter what fires it.

Basicly what I wanted is to block until that event fired. Similiar to what is possible to wait on a DOM node in chromedp

@mvdan mvdan changed the title How to Listen to javascript events? Support a way to listen for javascript events May 8, 2019
@mvdan
Copy link
Contributor

mvdan commented May 8, 2019

Thanks. I'm leaving the "needs info" label on, because I still don't know how this is possible with the devtools protocol.

@pmurley
Copy link
Contributor

pmurley commented May 8, 2019

I'm also not aware of a clean way to do this with DTP, but I think you could probably hack together a solution with a breakpoint - maybe using DomDebugger.setEventListenerBreakpoint ?

@mvdan
Copy link
Contributor

mvdan commented May 8, 2019

If anyone figures out a way and wants to post about it here, that would be helpful. This issue is not at the top of my priority list for now.

@tmm1
Copy link
Contributor

tmm1 commented May 18, 2019

How does puppeteer handle this?

@waltershowalter
Copy link

I was able to code this POF on 0.3.0. Doesn't work on 0.1.2, which we still have a bunch of chromedp code for testing or UI

I took this from some of the code shown here and other piecemeal stuff: https://godoc.org/github.com/chromedp/chromedp#ListenTarget

And this seems to capture javascript errors in addition to the other chromedp tests we have. For the time being, this code will live in a separate repo since dep ensure we're locked on 0.1.2 with our old ChromeDP calls wrapped in GoConvey for BDD style testing.

As a FYI, this works but crudely, but I could see further maturation - if it's a priority. Otherwise, I may try puppeteer too, but this actually works for some tests our director wanted recently, because we want to crawl our webpages and see what javascript errors are introduced with the latest checkins.

And, of course private info has been replaced with public strings like username/pword, URLs

package main

import (
"io"
"fmt"
"context"
"strings"
"net/http"
"net/http/httptest"
"time"

"github.com/chromedp/cdproto/page"
cdpruntime "github.com/chromedp/cdproto/runtime"
//"github.com/chromedp/cdproto/target"
"github.com/chromedp/chromedp"

)

var (

cancel context.CancelFunc
err    error

)

func listenTargetWithStage() {

ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()

chromedp.ListenTarget(ctx, func(ev interface{}) {
	switch ev := ev.(type) {
	case *cdpruntime.EventExceptionThrown:
		fmt.Printf("Event Exception, console time > %s \n", ev.Timestamp.Time())
		fmt.Printf("\tException Type > %s \n", ev.ExceptionDetails.Exception.Type.String())
		fmt.Printf("\tException Description > %s \n", ev.ExceptionDetails.Exception.Description)
		fmt.Printf("\tException Stacktrace Text > %s \n", ev.ExceptionDetails.Exception.ClassName)
	case *cdpruntime.StackTrace:
		fmt.Printf("Stack Trace, console type > %s \n", ev.Description)
		for _, frames := range ev.CallFrames {
			fmt.Printf("Frame line # %s\n", frames.LineNumber)
		}
	case *cdpruntime.EventConsoleAPICalled:
		fmt.Printf("Event Console API Called, console type > %s call:\n", ev.Type)
		for _, arg := range ev.Args {
			fmt.Printf("%s - %s\n", arg.Type, arg.Value)
		}

	}

})

if err := chromedp.Run(ctx,
	chromedp.Navigate("https://our.website.com"),
	chromedp.SendKeys(`#username`, "user"),
	chromedp.SendKeys(`#password`, "pword"),
	chromedp.Click(`#login-submit`),
	chromedp.Sleep(1 * time.Second),
	chromedp.Navigate("[another page]"),
	chromedp.Sleep(1 * time.Second),
); err != nil {
	panic(err)
}

}

func writeHTML(content string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
io.WriteString(w, strings.TrimSpace(content))
})
}

func main() {

listenTargetWithStage()

}

@kenshaw
Copy link
Member

kenshaw commented Jun 17, 2019

This can be implemented with the chromedp.ListenTarget func. See: https://github.com/GoogleChrome/puppeteer/blob/master/examples/custom-event.js which has an overview of how Puppeteer uses this. I'll make a point of adding this in chromedp/examples.

@kenshaw kenshaw added this to the v0.4.0 milestone Jul 2, 2019
@mvdan mvdan removed the needs info label Sep 1, 2019
@mvdan mvdan removed this from the v0.5.0 milestone Nov 14, 2019
@ZekeLu
Copy link
Member

ZekeLu commented May 3, 2021

With chromedp.Poll, we can do it like this:

// github.com/chromedp/chromedp v0.7.1
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"time"

	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {
	// create a test server to serve the page
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprint(w, `
<html lang="en">
<head>
    <script>
        setTimeout(() => {
            document.dispatchEvent(new Event('myCustomEvent'))
        }, 3000)
    </script>
</head>
<body>
</body>
</html>
`,
		)
	}))
	defer ts.Close()

	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()
	ctx, cancel = context.WithTimeout(ctx, time.Minute)
	defer cancel()

	var timestamp int

	err := chromedp.Run(ctx,
		chromedp.ActionFunc(func(ctx context.Context) error {
			_, err := page.AddScriptToEvaluateOnNewDocument("__fired__ = null; document.addEventListener('myCustomEvent', (e) => {__fired__ = Date.now();})").
				Do(ctx)
			return err
		}),
		chromedp.Navigate(ts.URL),
		chromedp.Poll("__fired__", &timestamp, chromedp.WithPollingInterval(time.Second)),
	)
	if err != nil {
		log.Fatal(err)
	}

	log.Print(timestamp)
}

The key part is to subscribe to the event with javascript code, and when the event is fired, set the value of a javascript variable. Then use chromedp.Poll to check and wait for this javascript variable becomes truthy.

@ZekeLu ZekeLu closed this as completed May 20, 2021
@daaku
Copy link

daaku commented Oct 12, 2022

For anyone looking for something similar that doesn't involve polling, here's a staring point that does a limited version of the exposeFunction feature from Puppeteer:

func ExposeFunc(name string, f func(string)) chromedp.Action {
	return chromedp.Tasks{
		chromedp.ActionFunc(func(ctx context.Context) error {
			chromedp.ListenTarget(ctx, func(ev interface{}) {
				if ev, ok := ev.(*cdruntime.EventBindingCalled); ok && ev.Name == name {
					f(ev.Payload)
				}
			})
			return nil
		}),
		cdruntime.AddBinding(name),
	}
}

The exposed function will be a global in every target and is available regardless of navigation. This is a one-way street which just accepts a single string argument. The Puppeteer version provides a fully transparent proxy with support for arguments and return values, which this does not.

@porjo
Copy link

porjo commented Feb 14, 2023

@daaku could you please extend your example to show how ExposeFunc() would be used to listen for a Javascript event?

@kenshaw adding an example to chromedp/examples would be a welcome addition

@ZekeLu
Copy link
Member

ZekeLu commented Feb 15, 2023

@porjo Please check the doc for Runtime.addBinding.

And see pr #1222.

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

No branches or pull requests

10 participants