From 03f9fab5d92071c7e37bfb5f4ad2474c1172d5bc Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Tue, 20 May 2025 17:00:06 +0200 Subject: [PATCH 1/2] chromedp click wip --- chromedp/click/main.go | 135 +++++++++++++++++++++++++++++++++++++++++ runner/main.go | 1 + 2 files changed, 136 insertions(+) create mode 100644 chromedp/click/main.go diff --git a/chromedp/click/main.go b/chromedp/click/main.go new file mode 100644 index 0000000..f5d3549 --- /dev/null +++ b/chromedp/click/main.go @@ -0,0 +1,135 @@ +// Copyright 2023-2025 Lightpanda (Selecy SAS) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "io" + "log" + "log/slog" + "os" + + "github.com/chromedp/chromedp" +) + +const ( + exitOK = 0 + exitFail = 1 +) + +// main starts interruptable context and runs the program. +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := run(ctx, os.Args, os.Stdout, os.Stderr) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(exitFail) + } + + os.Exit(exitOK) +} + +const ( + CdpWSDefault = "ws://127.0.0.1:9222" +) + +func run(ctx context.Context, args []string, stdout, stderr io.Writer) error { + // declare runtime flag parameters. + flags := flag.NewFlagSet(args[0], flag.ExitOnError) + flags.SetOutput(stderr) + + var ( + verbose = flags.Bool("verbose", false, "enable debug log level") + cdpws = flags.String("cdp", env("CDPCLI_WS", CdpWSDefault), "cdp ws to connect") + ) + + // usage func declaration. + exec := args[0] + flags.Usage = func() { + fmt.Fprintf(stderr, "usage: %s ]\n", exec) + fmt.Fprintf(stderr, "chromedp fetch url.\n") + fmt.Fprintf(stderr, "\nCommand line options:\n") + flags.PrintDefaults() + fmt.Fprintf(stderr, "\nEnvironment vars:\n") + fmt.Fprintf(stderr, "\tCDPCLI_WS\tdefault %s\n", CdpWSDefault) + } + if err := flags.Parse(args[1:]); err != nil { + return err + } + + if *verbose { + slog.SetLogLoggerLevel(slog.LevelDebug) + } + + args = flags.Args() + if len(args) != 1 { + return errors.New("url is required") + } + url := args[0] + + ctx, cancel := chromedp.NewRemoteAllocator(ctx, + *cdpws, chromedp.NoModifyURL, + ) + defer cancel() + + // build context options + var opts []chromedp.ContextOption + if *verbose { + opts = append(opts, chromedp.WithDebugf(log.Printf)) + } + + ctx, cancel = chromedp.NewContext(ctx, opts...) + defer cancel() + + // ensure the first tab is created + if err := chromedp.Run(ctx); err != nil { + return fmt.Errorf("new tab: %w", err) + } + + err := chromedp.Run(ctx, chromedp.Navigate(url)) + if err != nil { + return fmt.Errorf("navigate %s: %w", url, err) + } + + var currentURL string + err = chromedp.Run(ctx, + chromedp.Click("a[href='campfire-commerce/']", chromedp.ByQuery), + // wait or sleep? + chromedp.Location(¤tURL), + ) + if err != nil { + return fmt.Errorf("click: %w", err) + } + + if currentURL != "http://127.0.0.1:1234/campfire-commerce/" { + log.Fatal("The new page URL is not as expected.") + } + + return nil +} + +// env returns the env value corresponding to the key or the default string. +func env(key, dflt string) string { + val, ok := os.LookupEnv(key) + if !ok { + return dflt + } + + return val +} diff --git a/runner/main.go b/runner/main.go index f134bad..6a36849 100644 --- a/runner/main.go +++ b/runner/main.go @@ -106,6 +106,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) error { {Bin: "node", Args: []string{"playwright/click.js"}}, {Bin: "go", Args: []string{"run", "fetch/main.go", "http://127.0.0.1:1234/"}, Dir: "chromedp"}, {Bin: "go", Args: []string{"run", "links/main.go", "http://127.0.0.1:1234/"}, Dir: "chromedp"}, + {Bin: "go", Args: []string{"run", "click/main.go", "http://127.0.0.1:1234/"}, Dir: "chromedp"}, } { if *verbose { t.Stderr = stderr From ebb95080af76ec11a39a53bcbcc7be2363cea6bd Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 21 May 2025 11:01:02 +0200 Subject: [PATCH 2/2] Additional result validation --- chromedp/click/main.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/chromedp/click/main.go b/chromedp/click/main.go index f5d3549..8cf0c07 100644 --- a/chromedp/click/main.go +++ b/chromedp/click/main.go @@ -63,7 +63,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) error { exec := args[0] flags.Usage = func() { fmt.Fprintf(stderr, "usage: %s ]\n", exec) - fmt.Fprintf(stderr, "chromedp fetch url.\n") + fmt.Fprintf(stderr, "chromedp fetch url and click on `campfire-commerce`.\n") fmt.Fprintf(stderr, "\nCommand line options:\n") flags.PrintDefaults() fmt.Fprintf(stderr, "\nEnvironment vars:\n") @@ -102,23 +102,37 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) error { return fmt.Errorf("new tab: %w", err) } - err := chromedp.Run(ctx, chromedp.Navigate(url)) + // Navigate and click on the link + err := chromedp.Run(ctx, + chromedp.Navigate(url), + chromedp.Click("a[href='campfire-commerce/']", chromedp.ByQuery), + ) if err != nil { - return fmt.Errorf("navigate %s: %w", url, err) + return fmt.Errorf("click: %w", err) } + // Validation var currentURL string + var priceText string + var reviewNames []string + var reviewTexts []string err = chromedp.Run(ctx, - chromedp.Click("a[href='campfire-commerce/']", chromedp.ByQuery), - // wait or sleep? chromedp.Location(¤tURL), + chromedp.Text("#product-price", &priceText, chromedp.NodeVisible, chromedp.ByQuery), + chromedp.Evaluate(`Array.from(document.querySelectorAll('#product-reviews > div h4')).map(e => e.textContent)`, &reviewNames), + chromedp.Evaluate(`Array.from(document.querySelectorAll('#product-reviews > div p')).map(e => e.textContent)`, &reviewTexts), ) if err != nil { - return fmt.Errorf("click: %w", err) + return fmt.Errorf("checks failed: %w", err) } - if currentURL != "http://127.0.0.1:1234/campfire-commerce/" { - log.Fatal("The new page URL is not as expected.") + return errors.New("the new page URL is not as expected") + } + if priceText != "$244.99" { + return fmt.Errorf("incorrect product price: %s", priceText) + } + if len(reviewNames) != 3 || len(reviewTexts) != 3 { + return errors.New("incorrect reviews count") } return nil