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

chromedp used as a functional test tool? #115

Closed
stephencheng opened this Issue Aug 31, 2017 · 14 comments

Comments

Projects
None yet
4 participants
@stephencheng
Copy link

stephencheng commented Aug 31, 2017

I am planning to use chromedp as a functional test tool.

A couple of things to address:

  1. I do not know how to quit/force timeout of: WaitVisible. Sometimes for unexpected behaviours, chromedp will just wait forever and then goes to end itself. I need to understand if there is a mechanism that I can timeout or even retry it and I could decide if the execution continue or ignore current step.

  2. I will need WaitXXXX method or whatever other method could be to give me a return value of True/False, so that I could determine next step. What is this method?

  3. I will need to have the control of the workflow, so that it won't just throw an error, cancel or kill itself. So that I could go through a list of functional tests steps to gather the result of (yes/no/ignored) etc.

  4. Possible I could use any existing framework to assert etc?

Please anyone could hint how to achieve above?

@AmrAlfoly

This comment has been minimized.

Copy link

AmrAlfoly commented Oct 5, 2017

i am in the exact same situation you are in , and digging all around to find answers, but it seems that chromedp lacks proper documentations and examples and the owner doesn't seem interested in answering questions

@kenshaw

This comment has been minimized.

Copy link
Contributor

kenshaw commented Oct 5, 2017

I disagree, but sure if you say so.

I agree the documentation and examples are sparse, but using chromedp isn't exactly for the faint of heart. There's a certain amount of investment that a user of the chromedp package will have to make into understanding the Chrome Debugging Protocol, as well as how the chromedp package is laid out.

I would be great, @AmrAlfoly, if you were able to submit / write up some example articles on how to use chromedp.

@AmrAlfoly

This comment has been minimized.

Copy link

AmrAlfoly commented Oct 5, 2017

@kenshaw i don't want to be an expert in chromedp or even the Chrome Debugging Protocol, it's a package , and we all deal with packages as with there API , i don't need to understand all of it's corners or the corners of it's foundations .
i don't have the time to write examples on how to use chromedp plus it's hard hard to write examples about a package only it's owner understand it and even refuses to share his knowledge with his package users plus looking down on them

@yogesh-desai

This comment has been minimized.

Copy link

yogesh-desai commented Oct 5, 2017

I think getting a late reply is not that bad at all. As everyone has their own work to do on priority. I have spent a couple of hours to understand the Chrome Debugging Protocol and chromedp though I was new to the domain. It's really worth to do that as after that it's really easy to do the things/ experiments with this package.
Anyway, Thanks to @kenshaw who made this available. 🥇

@AmrAlfoly

This comment has been minimized.

Copy link

AmrAlfoly commented Oct 6, 2017

@yogesh-desai

I have spent a couple of hours to understand the Chrome Debugging Protocol and chromedp though I was new to the domain

are you sure about that ?
if you are so please share the resources that made you in 2 hours understand both of them
regarding that you as a man who understood both of them in 2 hours couldn't provide an answer for a very simple question that's while using selenium was like the air
(stop scratching it to the wall, it can go bloody very quickly)

@stephencheng

This comment has been minimized.

Copy link
Author

stephencheng commented Oct 6, 2017

This tool is great. Personal experience would be painful in the beginning especially a beginner golang user tool.

I spent quite some time to trace into details so that to figure out single little problem. Learning wise, I'm paid off and rewarded with the result. However if you are treating this package as a software product, it's pretty bad. Documents and examples are a little outdated; not too much responding from author or other users; no idea where this project is going ahead and the road map etc; no community and for myself it's feeling battling alone using it.

I think @kenshaw has done great job for this project but lack of time to do everything. I am happy to contribute either to the code, bug fix, example, documents etc as long as @kenshaw has time to respond or call it out.

@kenshaw

This comment has been minimized.

Copy link
Contributor

kenshaw commented Oct 6, 2017

I'm sorry if I have disappointed people with chromedp. I try to support what I've put out there, but I am definitely constrained on time. For what it's worth, chromedp works marvelously for the specific use cases that it was designed / written for -- ie driving Chrome in a headless context. chromedp is a minor part of a much larger toolchain we've built at Brankas. We open sourced it because I had thought the community at large would find it useful.

That said, driving Chrome directly using chromedp definitely requires some knowledge of/familiarity with the underlying protocol, as well as a fairly strong grasp of functional coding principles and Go. It's a small tool to simplify/standardize communication with a Chromium-based web browser. While many developers might find the way Action/Task hierarchies are composed with chromedp a bit different than what they're used to with other languages or browser-driving frameworks, much of the way chromedp was written was due to specific requirements of writing it in Go and still making it efficient. Also, it's worth noting that of all the other Chrome Debugging Protocol packages available in Go, only chromedp provides composable, high-level actions out of the box -- that's actually quite the feat given that chromedp is 100% type-safe, lightning fast, and supports 100% of the Chrome Debugging Protocol. None of the other Go based packages that support the Chrome Debugging Protocol, as far as I'm aware, can claim all of those.

Honestly, I'd like to make chromedp more useful to a larger audience, but I really don't know really how that's possible. "Most" general functionality one would use on a regular basis is already in the package -- ie, things like running / executing Javascript, taking screenshots, finding a specific element on the page, modifying data, submitting forms, etc. If there's anything else that's needed, then you have the entirety of the underlying Chrome Debugging Protocol available at your fingertips. Because of the way that chromedp has been designed, it means that most important/useful CDP commands can be easily combined together via the Action and Tasks types to drive Chrome and allow you to continue using Go as the language for driving Chrome.

Where a developer new to chromedp, Go, and this style of functional coding might get messed up, is in "how" one organizes/writes high-level logic. For that, the answer is to simply use Go:

if err := c.Run(myContext, myAction1(&res)); err != nil {
// we failed, so do something
} else if res == "something" {
// we got "something", so respond to that ...
}

(where c.Run above is simply a call to chromedp/CDP.Run)

I realize the above is not explicitly obvious to developers who are simultaneously new to Go, the Chrome Debugging Protocol, chromedp and functional logic based program flow. It takes getting used to, but after a while this style grows on you, and after digging around the CDP protocol for a while, you realize there really isn't a better way to manage all the hundreds of API calls and types available in the CDP, and make it fast, safe, and efficient in Go.

@stephencheng

This comment has been minimized.

Copy link
Author

stephencheng commented Oct 6, 2017

Thanks @kenshaw for sharing. I am certainly a believer of what you have claimed. Compared to using all sorts of different drivers, the goland is pure and clean and no messy os pkg dependencies hell to manage. Compared with phantomjs/casperjs, its performance is like 10 times better.

The code presented in a clean hierarchy structure and it's easy to understand, but may need more time to dive into. The most difficult part for new user is the understanding itself. This needs documents/wiki pages etc or more communications. The code itself is always the secondary to be worried about.

What you have pointed above and the thoughts is exactly what I am needing for. If I get the idea, I can just make it work accordingly.

Thanks again.

@AmrAlfoly

This comment has been minimized.

Copy link

AmrAlfoly commented Oct 6, 2017

@kenshaw i don't know what to say?
when i got stuck i went to open an issue which you closed without responding to it
then i went to ask people at stackoverflow , google+ , golang forum
event i followed you on twitter and tweeted my question to you
a simple descriptive answer could be more than enough from the first time
please forgive my attitude and behavior
(i loved golang so i am transferring my code base from python to golang and for my tests i am transferring from python + selenium to golang + chromedp)
that's why i was mad because i couldn't find any one to help me and i thought you were ignoring my questions

@kenshaw

This comment has been minimized.

Copy link
Contributor

kenshaw commented Oct 7, 2017

I've added a more complex logic example in the source as example/logic/main.go:

// examples/logic/main.go
package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	cdp "github.com/knq/chromedp"
	cdptypes "github.com/knq/chromedp/cdp"
)

func main() {
	var err error

	// create context
	ctxt, cancel := context.WithCancel(context.Background())
	defer cancel()

	// create chrome instance
	c, err := cdp.New(ctxt, cdp.WithLog(log.Printf))
	if err != nil {
		log.Fatal(err)
	}

	// list awesome go projects for the "Selenium and browser control tools."
	res, err := listAwesomeGoProjects(ctxt, c, "Selenium and browser control tools.")
	if err != nil {
		log.Fatalf("could not list awesome go projects: %v", err)
	}

	// shutdown chrome
	err = c.Shutdown(ctxt)
	if err != nil {
		log.Fatal(err)
	}

	// wait for chrome to finish
	err = c.Wait()
	if err != nil {
		log.Fatal(err)
	}

	// output the values
	for k, v := range res {
		log.Printf("project %s (%s): '%s'", k, v.URL, v.Description)
	}
}

// ud contains a url, description for a project.
type ud struct {
	URL, Description string
}

// listAwesomeGoProjects is the highest level logic for browsing to the
// awesome-go page, finding the specified section sect, and retrieving the
// associated projects from the page.
func listAwesomeGoProjects(ctxt context.Context, c *cdp.CDP, sect string) (map[string]ud, error) {
	// force max timeout of 15 seconds for retrieving and processing the data
	var cancel func()
	ctxt, cancel = context.WithTimeout(ctxt, 25*time.Second)
	defer cancel()

	sel := fmt.Sprintf(`//p[text()[contains(., '%s')]]`, sect)

	// navigate
	if err := c.Run(ctxt, cdp.Navigate(`https://github.com/avelino/awesome-go`)); err != nil {
		return nil, fmt.Errorf("could not navigate to github: %v", err)
	}

	// wait visible
	if err := c.Run(ctxt, cdp.WaitVisible(sel)); err != nil {
		return nil, fmt.Errorf("could not get section: %v", err)
	}

	sib := sel + `/following-sibling::ul/li`

	// get project link text
	var projects []*cdptypes.Node
	if err := c.Run(ctxt, cdp.Nodes(sib+`/child::a/text()`, &projects)); err != nil {
		return nil, fmt.Errorf("could not get projects: %v", err)
	}

	// get links and description text
	var linksAndDescriptions []*cdptypes.Node
	if err := c.Run(ctxt, cdp.Nodes(sib+`/child::node()`, &linksAndDescriptions)); err != nil {
		return nil, fmt.Errorf("could not get links and descriptions: %v", err)
	}

	// check length
	if 2*len(projects) != len(linksAndDescriptions) {
		return nil, fmt.Errorf("projects and links and descriptions lengths do not match (2*%d != %d)", len(projects), len(linksAndDescriptions))
	}

	// process data
	res := make(map[string]ud)
	for i := 0; i < len(projects); i++ {
		res[projects[i].NodeValue] = ud{
			URL:         linksAndDescriptions[2*i].AttributeValue("href"),
			Description: strings.TrimPrefix(strings.TrimSpace(linksAndDescriptions[2*i+1].NodeValue), "- "),
		}
	}

	return res, nil
}
@stephencheng

This comment has been minimized.

Copy link
Author

stephencheng commented Oct 16, 2017

Hi @kenshaw

Could you spare your idea on this matter I'd like to resolve? or confirm this feature is not there yet.

See below code:

In the test1, I try to make a exception by Wait something which is not availabe at all: #foot-not-visible.

I am expecting it

  1. error out, with: cdp.WaitVisible(#footer-not-visible),
  2. exec : log.Fatal("test1 errored", err), and continue with test2() run.
  3. but it timed out and quit with error code 1 without continue further.

Please could you explicitly explain how to let this cdp to run multiple tasks without quit without user's control at all.

I just don't get it why it quit. Does it to do with this piece of code:

func (t Tasks) Do(ctxt context.Context, h cdp.Handler) error {
	var err error

	// TODO: put individual task timeouts from context here
	for _, a := range t {
		// ctxt, cancel = context.WithTimeout(ctxt, timeout)
		// defer cancel()
		err = a.Do(ctxt, h)
		if err != nil {
			return err
		}
	}

	return nil
}

=================log==================


2017/10/16 17:11:08 -> {"id":346,"result":{"searchId":"76510.1231","resultCount":0}}
2017/10/16 17:11:08 <- {"id":347,"method":"DOM.performSearch","params":{"query":"#footer-not-visible"}}
2017/10/16 17:11:08 -> {"id":347,"result":{"searchId":"76510.1232","resultCount":0}}
2017/10/16 17:11:08 <- {"id":348,"method":"DOM.performSearch","params":{"query":"#footer-not-visible"}}
2017/10/16 17:11:08 -> {"id":348,"result":{"searchId":"76510.1233","resultCount":0}}
2017/10/16 17:11:09 test1 erroredcontext deadline exceeded
exit status 1

=================my code==================

package main

import (
	"context"
	"log"
	"time"
	// "time"

	cdp "github.com/knq/chromedp"
)

func main() {
	var err error

	// create context
	// ctxt, cancel := context.WithCancel(context.Background())
	ctxt, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()

	// create chrome instance
	c, err := cdp.New(ctxt, cdp.WithLog(log.Printf))
	if err != nil {
		log.Fatal(err)
	}

	log.Println("=======================test1==========================")
	// run task list
	err = c.Run(ctxt, test1())
	if err != nil {
		log.Fatal("test1 errored", err)
	}

	log.Println("=======================test2==========================")
	err = c.Run(ctxt, test2())
	if err != nil {
		log.Fatal("test2 errored", err)
	}

	// shutdown chrome
	err = c.Shutdown(ctxt)
	if err != nil {
		log.Fatal(err)
	}

	// wait for chrome to finish
	err = c.Wait()
	if err != nil {
		log.Fatal(err)
	}
	// cancel()
}

func test1() cdp.Tasks {
	return cdp.Tasks{
		cdp.Navigate(`https://golang.org/pkg/time/`),
		cdp.WaitVisible(`#footer-not-visible`),
		cdp.Click(`#pkg-overview`, cdp.NodeVisible),
		// cdp.Sleep(10 * time.Second),
	}
}

func test2() cdp.Tasks {
	return cdp.Tasks{
		cdp.Navigate(`https://golang.org/pkg/time/`),
		cdp.WaitVisible(`#footer`),

		cdp.Click(`#pkg-overview`, cdp.NodeVisible),
		// cdp.Sleep(10 * time.Second),
	}
}

@stephencheng stephencheng reopened this Oct 16, 2017

@kenshaw

This comment has been minimized.

Copy link
Contributor

kenshaw commented Oct 16, 2017

From this:

	ctxt, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()

You're forcing the entire operation to finish in 20 seconds or less. Even on a superfast system, with superfast network connectivity, 20 seconds is probably not enough to finish your system. That's why you're getting a deadline exceeded error.

If you'd like to learn more about how the context package works, there's a great article here -- https://blog.golang.org/context -- that I'd suggest reading.

@stephencheng

This comment has been minimized.

Copy link
Author

stephencheng commented Oct 16, 2017

@kenshaw , that 20 seconds was put there for testing purpose only, it's for the purpose of saving time, in most of the cases, a selector could just disappear because web site change etc.

Without the timeout 20 seconds, it's the same, like using below:
// ctxt, cancel := context.WithCancel(context.Background())

which will wait long enough to error and Exit(1).

This does not make sense at all, (if there is anything that I have not figure out or not entirely understand), I wish the operation could keep going ahead with test2(), how would I do that?

If I do not set timeout, why it's keep waiting for the element and then Bang and exploded?

I sincerely hope you could refer to a good implementation, such as in casper js:

        casper.thenOpen(url_order, function() {
            casper.waitForSelector("#this-is-selector",
                    function() {
                        this.evaluate(function(ord) {
                            $("#whatever).val(xyz);
                            $("#hahalink").click();
                        },
                        {
                            avar: myvar
                        });

                    },
                    function() {
                        this.echo("Timeout ...");
                    },
                    1000
                        );

        });


This function call allow user to do user level operation, also allow user to setup a operation step level timeout, 1 second, and supply a function call to handle error, in this case, to echo out("timeout")

Is similar thing could be achieved in CDP? how do I do it?

Really need to tackle this. And I believe many others would expect the same.

Thank you in advance. @kenshaw

@stephencheng

This comment has been minimized.

Copy link
Author

stephencheng commented Oct 16, 2017

@kenshaw I think I got what's going on there.

To summarise, a few things:

  1. for above case, the log.Fatal is to blame and did the Exit 1.
  2. the operational boundary in CDP is to use context to segregate each different tasks group as atomic step, hence you will need to wrap new operation action around a new context rather than the oudated(canceled) one.

Thanks for pointing it out.

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