1 package playwright_test
2
3 import (
4 "bytes"
5 "io/ioutil"
6 "log"
7 "mime"
8 "net"
9 "net/http"
10 "net/http/httptest"
11 "os"
12 "path/filepath"
13 "reflect"
14 "strings"
15 "sync"
16 "testing"
17
18 "github.com/playwright-community/playwright-go"
19 "github.com/stretchr/testify/require"
20 )
21
22 var pw *playwright.Playwright
23 var browser playwright.Browser
24 var context playwright.BrowserContext
25 var page playwright.Page
26 var isChromium bool
27 var isFirefox bool
28 var isWebKit bool
29 var browserName = getBrowserName()
30 var server *testServer
31 var browserType playwright.BrowserType
32 var utils *testUtils
33
34 func init() {
35 if err := mime.AddExtensionType(".js", "application/javascript"); err != nil {
36 log.Fatalf("could not add mime extension type: %v", err)
37 }
38 }
39
40 func TestMain(m *testing.M) {
41 BeforeAll()
42 code := m.Run()
43 AfterAll()
44 os.Exit(code)
45 }
46
47 func BeforeAll() {
48 var err error
49 pw, err = playwright.Run()
50 if err != nil {
51 log.Fatalf("could not start Playwright: %v", err)
52 }
53 if browserName == "chromium" || browserName == "" {
54 browserType = pw.Chromium
55 } else if browserName == "firefox" {
56 browserType = pw.Firefox
57 } else if browserName == "webkit" {
58 browserType = pw.WebKit
59 }
60 browser, err = browserType.Launch(playwright.BrowserTypeLaunchOptions{
61 Headless: playwright.Bool(os.Getenv("HEADFUL") == ""),
62 })
63 if err != nil {
64 log.Fatalf("could not launch: %v", err)
65 }
66 isChromium = browserName == "chromium" || browserName == ""
67 isFirefox = browserName == "firefox"
68 isWebKit = browserName == "webkit"
69 server = newTestServer()
70 utils = &testUtils{}
71 }
72
73 func AfterAll() {
74 server.testServer.Close()
75 if err := pw.Stop(); err != nil {
76 log.Fatalf("could not start Playwright: %v", err)
77 }
78 }
79
80 var DEFAULT_CONTEXT_OPTIONS = playwright.BrowserNewContextOptions{
81 AcceptDownloads: playwright.Bool(true),
82 HasTouch: playwright.Bool(true),
83 }
84
85 func BeforeEach(t *testing.T) {
86 newContextWithOptions(t, DEFAULT_CONTEXT_OPTIONS)
87 }
88
89 func newContextWithOptions(t *testing.T, contextOptions playwright.BrowserNewContextOptions) {
90 var err error
91 context, err = browser.NewContext(contextOptions)
92 require.NoError(t, err)
93 page, err = context.NewPage()
94 require.NoError(t, err)
95 }
96
97 func Asset(path string) string {
98 cwd, err := os.Getwd()
99 if err != nil {
100 log.Fatalf("could not get cwd: %v", err)
101 }
102 return filepath.Join(cwd, "assets", path)
103 }
104
105 func AfterEach(t *testing.T, closeContext ...bool) {
106 if len(closeContext) == 0 {
107 if err := context.Close(); err != nil {
108 t.Errorf("could not close context: %v", err)
109 }
110 }
111 server.AfterEach()
112 }
113
114 func newTestServer() *testServer {
115 ts := &testServer{
116 routes: make(map[string]http.HandlerFunc),
117 requestSubscriberes: make(map[string][]chan *http.Request),
118 }
119 ts.testServer = httptest.NewServer(http.HandlerFunc(ts.serveHTTP))
120 ts.PREFIX = ts.testServer.URL
121 ts.EMPTY_PAGE = ts.testServer.URL + "/empty.html"
122 ts.CROSS_PROCESS_PREFIX = strings.Replace(ts.testServer.URL, "127.0.0.1", "localhost", 1)
123 return ts
124 }
125
126 type testServer struct {
127 sync.Mutex
128 testServer *httptest.Server
129 routes map[string]http.HandlerFunc
130 requestSubscriberes map[string][]chan *http.Request
131 PREFIX string
132 EMPTY_PAGE string
133 CROSS_PROCESS_PREFIX string
134 }
135
136 func (t *testServer) AfterEach() {
137 t.Lock()
138 defer t.Unlock()
139 t.routes = make(map[string]http.HandlerFunc)
140 t.requestSubscriberes = make(map[string][]chan *http.Request)
141 }
142
143 func (t *testServer) serveHTTP(w http.ResponseWriter, r *http.Request) {
144 t.Lock()
145 defer t.Unlock()
146 body, err := ioutil.ReadAll(r.Body)
147 if err != nil {
148 log.Printf("Error reading body: %v", err)
149 http.Error(w, "can't read body", http.StatusBadRequest)
150 return
151 }
152 r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
153 if handlers, ok := t.requestSubscriberes[r.URL.Path]; ok {
154 for _, handler := range handlers {
155 handler <- r
156 }
157 }
158 if route, ok := t.routes[r.URL.Path]; ok {
159 route(w, r)
160 return
161 }
162 w.Header().Add("Cache-Control", "no-cache, no-store")
163 http.FileServer(http.Dir("assets")).ServeHTTP(w, r)
164 }
165
166 func (s *testServer) SetRoute(path string, f http.HandlerFunc) {
167 s.Lock()
168 defer s.Unlock()
169 s.routes[path] = f
170 }
171
172 func (s *testServer) SetRedirect(from, to string) {
173 s.SetRoute(from, func(w http.ResponseWriter, r *http.Request) {
174 http.Redirect(w, r, to, http.StatusFound)
175 })
176 }
177
178 func (s *testServer) WaitForRequestChan(path string) <-chan *http.Request {
179 s.Lock()
180 defer s.Unlock()
181 if _, ok := s.requestSubscriberes[path]; !ok {
182 s.requestSubscriberes[path] = make([]chan *http.Request, 0)
183 }
184 channel := make(chan *http.Request, 1)
185 s.requestSubscriberes[path] = append(s.requestSubscriberes[path], channel)
186 return channel
187 }
188
189 func Map(vs interface{}, f func(interface{}) interface{}) []interface{} {
190 v := reflect.ValueOf(vs)
191 vsm := make([]interface{}, v.Len())
192 for i := 0; i < v.Len(); i++ {
193 vsm[i] = f(v.Index(i).Interface())
194 }
195 return vsm
196 }
197
198
199
200
201
202 func ChanToSlice(ch interface{}, amount int) interface{} {
203 chv := reflect.ValueOf(ch)
204 slv := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(ch).Elem()), 0, 0)
205 for i := 0; i < amount; i++ {
206 v, ok := chv.Recv()
207 if !ok {
208 return slv.Interface()
209 }
210 slv = reflect.Append(slv, v)
211 }
212 return slv.Interface()
213 }
214
215 type syncSlice struct {
216 sync.Mutex
217 slice []interface{}
218 }
219
220 func (s *syncSlice) Append(v interface{}) {
221 s.Lock()
222 s.slice = append(s.slice, v)
223 s.Unlock()
224 }
225
226 func (s *syncSlice) Get() interface{} {
227 s.Lock()
228 defer s.Unlock()
229 return s.slice
230 }
231
232 func newSyncSlice() *syncSlice {
233 return &syncSlice{
234 slice: make([]interface{}, 0),
235 }
236 }
237
238 type testUtils struct {
239 }
240
241 func (t *testUtils) AttachFrame(page playwright.Page, frameId string, url string) (playwright.Frame, error) {
242 _, err := page.EvaluateHandle(`async ({ frame_id, url }) => {
243 const frame = document.createElement('iframe');
244 frame.src = url;
245 frame.id = frame_id;
246 document.body.appendChild(frame);
247 await new Promise(x => frame.onload = x);
248 return frame;
249 }`, map[string]interface{}{
250 "frame_id": frameId,
251 "url": url,
252 })
253 if err != nil {
254 return nil, err
255 }
256 return nil, nil
257 }
258
259 func (tu *testUtils) VerifyViewport(t *testing.T, page playwright.Page, width, height int) {
260 require.Equal(t, page.ViewportSize().Width, width)
261 require.Equal(t, page.ViewportSize().Height, height)
262 innerWidth, err := page.Evaluate("window.innerWidth")
263 require.NoError(t, err)
264 require.Equal(t, innerWidth, width)
265 innerHeight, err := page.Evaluate("window.innerHeight")
266 require.NoError(t, err)
267 require.Equal(t, innerHeight, height)
268 }
269
270 func (tu *testUtils) AssertEval(t *testing.T, page playwright.Page, script string, expected interface{}) {
271 result, err := page.Evaluate(script)
272 require.NoError(t, err)
273 require.Equal(t, expected, result)
274 }
275
276 func getBrowserName() string {
277 browserName, hasEnv := os.LookupEnv("BROWSER")
278 if hasEnv {
279 return browserName
280 }
281 return "chromium"
282 }
283
284 func getFreePort() (int, error) {
285 addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
286 if err != nil {
287 return 0, err
288 }
289
290 l, err := net.ListenTCP("tcp", addr)
291 if err != nil {
292 return 0, err
293 }
294 defer l.Close()
295 return l.Addr().(*net.TCPAddr).Port, nil
296 }
297
View as plain text