Skip to content

Commit 58ea8c6

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

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

chromedp/ri/main.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
"time"
28+
29+
"github.com/chromedp/cdproto/cdp"
30+
"github.com/chromedp/cdproto/fetch"
31+
"github.com/chromedp/chromedp"
32+
)
33+
34+
const (
35+
exitOK = 0
36+
exitFail = 1
37+
)
38+
39+
// main starts interruptable context and runs the program.
40+
func main() {
41+
ctx, cancel := context.WithCancel(context.Background())
42+
defer cancel()
43+
44+
err := run(ctx, os.Args, os.Stdout, os.Stderr)
45+
if err != nil {
46+
fmt.Fprintln(os.Stderr, err.Error())
47+
os.Exit(exitFail)
48+
}
49+
50+
os.Exit(exitOK)
51+
}
52+
53+
const (
54+
CdpWSDefault = "ws://127.0.0.1:9222"
55+
)
56+
57+
func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {
58+
// declare runtime flag parameters.
59+
flags := flag.NewFlagSet(args[0], flag.ExitOnError)
60+
flags.SetOutput(stderr)
61+
62+
var (
63+
verbose = flags.Bool("verbose", false, "enable debug log level")
64+
cdpws = flags.String("cdp", env("CDPCLI_WS", CdpWSDefault), "cdp ws to connect")
65+
)
66+
67+
// usage func declaration.
68+
exec := args[0]
69+
flags.Usage = func() {
70+
fmt.Fprintf(stderr, "usage: %s <url>]\n", exec)
71+
fmt.Fprintf(stderr, "chromedp fetch an url and intercept requests.\n")
72+
fmt.Fprintf(stderr, "\nCommand line options:\n")
73+
flags.PrintDefaults()
74+
fmt.Fprintf(stderr, "\nEnvironment vars:\n")
75+
fmt.Fprintf(stderr, "\tCDPCLI_WS\tdefault %s\n", CdpWSDefault)
76+
}
77+
if err := flags.Parse(args[1:]); err != nil {
78+
return err
79+
}
80+
81+
if *verbose {
82+
slog.SetLogLoggerLevel(slog.LevelDebug)
83+
}
84+
85+
args = flags.Args()
86+
if len(args) != 1 {
87+
return errors.New("url is required")
88+
}
89+
url := args[0]
90+
91+
ctx, cancel := chromedp.NewRemoteAllocator(ctx,
92+
*cdpws, chromedp.NoModifyURL,
93+
)
94+
defer cancel()
95+
96+
// build context options
97+
var opts []chromedp.ContextOption
98+
if *verbose {
99+
opts = append(opts, chromedp.WithDebugf(log.Printf))
100+
}
101+
102+
ctx, cancel = chromedp.NewContext(ctx, opts...)
103+
defer cancel()
104+
105+
// ensure the first tab is created
106+
if err := chromedp.Run(ctx); err != nil {
107+
return fmt.Errorf("new tab: %w", err)
108+
}
109+
110+
chromedp.ListenTarget(ctx, func(ev any) {
111+
switch ev := ev.(type) {
112+
case *fetch.EventRequestPaused:
113+
go func() {
114+
url := ev.Request.URL
115+
fmt.Fprintf(os.Stdout, "%s %s\n", ev.RequestID, url)
116+
117+
// alter the response with a new body
118+
if strings.HasSuffix(url, "/reviews.json") {
119+
encoded := base64.StdEncoding.EncodeToString([]byte(`["alter review"]`))
120+
_ = chromedp.Run(ctx,
121+
fetch.FulfillRequest(ev.RequestID, 200).WithBody(encoded),
122+
)
123+
return
124+
}
125+
126+
// by default let the request running.
127+
_ = chromedp.Run(ctx, fetch.ContinueRequest(ev.RequestID))
128+
}()
129+
}
130+
})
131+
132+
if err := chromedp.Run(ctx,
133+
fetch.Enable().WithPatterns(nil),
134+
); err != nil {
135+
log.Fatal(err)
136+
}
137+
138+
err := chromedp.Run(ctx, chromedp.Navigate(url))
139+
if err != nil {
140+
return fmt.Errorf("navigate %s: %w", url, err)
141+
}
142+
143+
var a []*cdp.Node
144+
if err := chromedp.Run(ctx,
145+
chromedp.Nodes(`#product-reviews > div > p`, &a,
146+
chromedp.Populate(1, false,
147+
chromedp.PopulateWait(50*time.Millisecond),
148+
),
149+
),
150+
); err != nil {
151+
return fmt.Errorf("get reviews: %w", err)
152+
}
153+
154+
reviews := make([]string, 0, len(a))
155+
for _, aa := range a {
156+
if len(aa.Children) != 1 {
157+
// should not happen, but it will be catched by the following
158+
// asserts.
159+
continue
160+
}
161+
reviews = append(reviews, aa.Children[0].NodeValue)
162+
}
163+
164+
fmt.Fprintf(os.Stdout, "%v\n", reviews)
165+
166+
if len(reviews) != 1 {
167+
return errors.New("invalid reviews number")
168+
}
169+
if reviews[0] != "alter review" {
170+
return errors.New("invalid reviews title")
171+
}
172+
173+
return nil
174+
}
175+
176+
// env returns the env value corresponding to the key or the default string.
177+
func env(key, dflt string) string {
178+
val, ok := os.LookupEnv(key)
179+
if !ok {
180+
return dflt
181+
}
182+
183+
return val
184+
}

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)