Skip to content

Commit f5a7bdf

Browse files
committed
add request interception test for chromedp
1 parent b1108f5 commit f5a7bdf

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

chromedp/ri/main.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright 2023-2025 Lightpanda (Selecy SAS)
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package main
15+
16+
import (
17+
"context"
18+
"encoding/base64"
19+
"errors"
20+
"flag"
21+
"fmt"
22+
"io"
23+
"log"
24+
"log/slog"
25+
"os"
26+
"strings"
27+
28+
"github.com/chromedp/cdproto/cdp"
29+
"github.com/chromedp/cdproto/fetch"
30+
"github.com/chromedp/chromedp"
31+
)
32+
33+
const (
34+
exitOK = 0
35+
exitFail = 1
36+
)
37+
38+
// main starts interruptable context and runs the program.
39+
func main() {
40+
ctx, cancel := context.WithCancel(context.Background())
41+
defer cancel()
42+
43+
err := run(ctx, os.Args, os.Stdout, os.Stderr)
44+
if err != nil {
45+
fmt.Fprintln(os.Stderr, err.Error())
46+
os.Exit(exitFail)
47+
}
48+
49+
os.Exit(exitOK)
50+
}
51+
52+
const (
53+
CdpWSDefault = "ws://127.0.0.1:9222"
54+
)
55+
56+
func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {
57+
// declare runtime flag parameters.
58+
flags := flag.NewFlagSet(args[0], flag.ExitOnError)
59+
flags.SetOutput(stderr)
60+
61+
var (
62+
verbose = flags.Bool("verbose", false, "enable debug log level")
63+
cdpws = flags.String("cdp", env("CDPCLI_WS", CdpWSDefault), "cdp ws to connect")
64+
)
65+
66+
// usage func declaration.
67+
exec := args[0]
68+
flags.Usage = func() {
69+
fmt.Fprintf(stderr, "usage: %s <url>]\n", exec)
70+
fmt.Fprintf(stderr, "chromedp fetch an url and intercept requests.\n")
71+
fmt.Fprintf(stderr, "\nCommand line options:\n")
72+
flags.PrintDefaults()
73+
fmt.Fprintf(stderr, "\nEnvironment vars:\n")
74+
fmt.Fprintf(stderr, "\tCDPCLI_WS\tdefault %s\n", CdpWSDefault)
75+
}
76+
if err := flags.Parse(args[1:]); err != nil {
77+
return err
78+
}
79+
80+
if *verbose {
81+
slog.SetLogLoggerLevel(slog.LevelDebug)
82+
}
83+
84+
args = flags.Args()
85+
if len(args) != 1 {
86+
return errors.New("url is required")
87+
}
88+
url := args[0]
89+
90+
ctx, cancel := chromedp.NewRemoteAllocator(ctx,
91+
*cdpws, chromedp.NoModifyURL,
92+
)
93+
defer cancel()
94+
95+
// build context options
96+
var opts []chromedp.ContextOption
97+
if *verbose {
98+
opts = append(opts, chromedp.WithDebugf(log.Printf))
99+
}
100+
101+
ctx, cancel = chromedp.NewContext(ctx, opts...)
102+
defer cancel()
103+
104+
// ensure the first tab is created
105+
if err := chromedp.Run(ctx); err != nil {
106+
return fmt.Errorf("new tab: %w", err)
107+
}
108+
109+
chromedp.ListenTarget(ctx, func(ev any) {
110+
switch ev := ev.(type) {
111+
case *fetch.EventRequestPaused:
112+
go func() {
113+
url := ev.Request.URL
114+
fmt.Fprintf(os.Stdout, "%s %s\n", ev.RequestID, url)
115+
116+
// alter the response with a new body
117+
if strings.HasSuffix(url, "/reviews.json") {
118+
encoded := base64.StdEncoding.EncodeToString([]byte(`["alter review"]`))
119+
_ = chromedp.Run(ctx,
120+
fetch.FulfillRequest(ev.RequestID, 200).WithBody(encoded),
121+
)
122+
return
123+
}
124+
125+
// by default let the request running.
126+
_ = chromedp.Run(ctx, fetch.ContinueRequest(ev.RequestID))
127+
}()
128+
}
129+
})
130+
131+
if err := chromedp.Run(ctx,
132+
fetch.Enable().WithPatterns(nil),
133+
); err != nil {
134+
log.Fatal(err)
135+
}
136+
137+
err := chromedp.Run(ctx, chromedp.Navigate(url))
138+
if err != nil {
139+
return fmt.Errorf("navigate %s: %w", url, err)
140+
}
141+
142+
var a []*cdp.Node
143+
if err := chromedp.Run(ctx,
144+
chromedp.Nodes(`#product-reviews > div > p`, &a, chromedp.Populate(1, false)),
145+
); err != nil {
146+
return fmt.Errorf("get reviews: %w", err)
147+
}
148+
149+
reviews := make([]string, 0, len(a))
150+
for _, aa := range a {
151+
if len(aa.Children) != 1 {
152+
// should not happen, but it will be catched by the following
153+
// asserts.
154+
continue
155+
}
156+
reviews = append(reviews, aa.Children[0].NodeValue)
157+
}
158+
159+
fmt.Fprintf(os.Stdout, "%v\n", reviews)
160+
161+
if len(reviews) != 1 {
162+
return errors.New("invalid reviews number")
163+
}
164+
if reviews[0] != "alter review" {
165+
return errors.New("invalid reviews title")
166+
}
167+
168+
return nil
169+
}
170+
171+
// env returns the env value corresponding to the key or the default string.
172+
func env(key, dflt string) string {
173+
val, ok := os.LookupEnv(key)
174+
if !ok {
175+
return dflt
176+
}
177+
178+
return val
179+
}

runner/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {
116116
{Bin: "go", Args: []string{"run", "fetch/main.go", "http://127.0.0.1:1234/campfire-commerce/"}, Dir: "chromedp"},
117117
{Bin: "go", Args: []string{"run", "links/main.go", "http://127.0.0.1:1234/campfire-commerce/"}, Dir: "chromedp"},
118118
{Bin: "go", Args: []string{"run", "click/main.go", "http://127.0.0.1:1234/"}, Dir: "chromedp"},
119+
{Bin: "go", Args: []string{"run", "ri/main.go", "http://127.0.0.1:1234/campfire-commerce/"}, Dir: "chromedp"},
119120
} {
120121
if *verbose {
121122
t.Stderr = stderr

0 commit comments

Comments
 (0)