github.com/pion/webrtc/v4@v4.0.1/e2e/e2e_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build e2e 5 // +build e2e 6 7 package main 8 9 import ( 10 "context" 11 "encoding/json" 12 "fmt" 13 "os" 14 "strconv" 15 "strings" 16 "testing" 17 "time" 18 19 "github.com/pion/webrtc/v4" 20 "github.com/pion/webrtc/v4/pkg/media" 21 "github.com/sclevine/agouti" 22 ) 23 24 var silentOpusFrame = []byte{0xf8, 0xff, 0xfe} // 20ms, 8kHz, mono 25 26 var drivers = map[string]func() *agouti.WebDriver{ 27 "Chrome": func() *agouti.WebDriver { 28 return agouti.ChromeDriver( 29 agouti.ChromeOptions("args", []string{ 30 "--headless", 31 "--disable-gpu", 32 "--no-sandbox", 33 }), 34 agouti.Desired(agouti.Capabilities{ 35 "loggingPrefs": map[string]string{ 36 "browser": "INFO", 37 }, 38 }), 39 ) 40 }, 41 } 42 43 func TestE2E_Audio(t *testing.T) { 44 for name, d := range drivers { 45 driver := d() 46 t.Run(name, func(t *testing.T) { 47 if err := driver.Start(); err != nil { 48 t.Fatalf("Failed to start WebDriver: %v", err) 49 } 50 ctx, cancel := context.WithCancel(context.Background()) 51 defer func() { 52 cancel() 53 time.Sleep(50 * time.Millisecond) 54 _ = driver.Stop() 55 }() 56 page, errPage := driver.NewPage() 57 if errPage != nil { 58 t.Fatalf("Failed to open page: %v", errPage) 59 } 60 if err := page.SetPageLoad(1000); err != nil { 61 t.Fatalf("Failed to load page: %v", err) 62 } 63 if err := page.SetImplicitWait(1000); err != nil { 64 t.Fatalf("Failed to set wait: %v", err) 65 } 66 67 chStarted := make(chan struct{}) 68 chSDP := make(chan *webrtc.SessionDescription) 69 chStats := make(chan stats) 70 go logParseLoop(ctx, t, page, chStarted, chSDP, chStats) 71 72 pwd, errPwd := os.Getwd() 73 if errPwd != nil { 74 t.Fatalf("Failed to get working directory: %v", errPwd) 75 } 76 if err := page.Navigate( 77 fmt.Sprintf("file://%s/test.html", pwd), 78 ); err != nil { 79 t.Fatalf("Failed to navigate: %v", err) 80 } 81 82 sdp := <-chSDP 83 pc, answer, track, errTrack := createTrack(*sdp) 84 if errTrack != nil { 85 t.Fatalf("Failed to create track: %v", errTrack) 86 } 87 defer func() { 88 _ = pc.Close() 89 }() 90 91 answerBytes, errAnsSDP := json.Marshal(answer) 92 if errAnsSDP != nil { 93 t.Fatalf("Failed to marshal SDP: %v", errAnsSDP) 94 } 95 var result string 96 if err := page.RunScript( 97 "pc.setRemoteDescription(JSON.parse(answer))", 98 map[string]interface{}{"answer": string(answerBytes)}, 99 &result, 100 ); err != nil { 101 t.Fatalf("Failed to run script to set SDP: %v", err) 102 } 103 104 go func() { 105 for { 106 if err := track.WriteSample( 107 media.Sample{Data: silentOpusFrame, Duration: time.Millisecond * 20}, 108 ); err != nil { 109 t.Errorf("Failed to WriteSample: %v", err) 110 return 111 } 112 select { 113 case <-time.After(20 * time.Millisecond): 114 case <-ctx.Done(): 115 return 116 } 117 } 118 }() 119 120 select { 121 case <-chStarted: 122 case <-time.After(5 * time.Second): 123 t.Fatal("Timeout") 124 } 125 126 <-chStats 127 var packetReceived [2]int 128 for i := 0; i < 2; i++ { 129 select { 130 case stat := <-chStats: 131 for _, s := range stat { 132 if s.Type != "inbound-rtp" { 133 continue 134 } 135 if s.Kind != "audio" { 136 t.Errorf("Unused track stat received: %+v", s) 137 continue 138 } 139 packetReceived[i] = s.PacketsReceived 140 } 141 case <-time.After(5 * time.Second): 142 t.Fatal("Timeout") 143 } 144 } 145 146 packetsPerSecond := packetReceived[1] - packetReceived[0] 147 if packetsPerSecond < 45 || 55 < packetsPerSecond { 148 t.Errorf("Number of OPUS packets is expected to be: 50/second, got: %d/second", packetsPerSecond) 149 } 150 }) 151 } 152 } 153 154 func TestE2E_DataChannel(t *testing.T) { 155 for name, d := range drivers { 156 driver := d() 157 t.Run(name, func(t *testing.T) { 158 if err := driver.Start(); err != nil { 159 t.Fatalf("Failed to start WebDriver: %v", err) 160 } 161 ctx, cancel := context.WithCancel(context.Background()) 162 defer func() { 163 cancel() 164 time.Sleep(50 * time.Millisecond) 165 _ = driver.Stop() 166 }() 167 168 page, errPage := driver.NewPage() 169 if errPage != nil { 170 t.Fatalf("Failed to open page: %v", errPage) 171 } 172 if err := page.SetPageLoad(1000); err != nil { 173 t.Fatalf("Failed to load page: %v", err) 174 } 175 if err := page.SetImplicitWait(1000); err != nil { 176 t.Fatalf("Failed to set wait: %v", err) 177 } 178 179 chStarted := make(chan struct{}) 180 chSDP := make(chan *webrtc.SessionDescription) 181 go logParseLoop(ctx, t, page, chStarted, chSDP, nil) 182 183 pwd, errPwd := os.Getwd() 184 if errPwd != nil { 185 t.Fatalf("Failed to get working directory: %v", errPwd) 186 } 187 if err := page.Navigate( 188 fmt.Sprintf("file://%s/test.html", pwd), 189 ); err != nil { 190 t.Fatalf("Failed to navigate: %v", err) 191 } 192 193 sdp := <-chSDP 194 pc, errPc := webrtc.NewPeerConnection(webrtc.Configuration{}) 195 if errPc != nil { 196 t.Fatalf("Failed to create peer connection: %v", errPc) 197 } 198 defer func() { 199 _ = pc.Close() 200 }() 201 202 chValid := make(chan struct{}) 203 pc.OnDataChannel(func(dc *webrtc.DataChannel) { 204 dc.OnOpen(func() { 205 // Ping 206 if err := dc.SendText("hello world"); err != nil { 207 t.Errorf("Failed to send data: %v", err) 208 } 209 }) 210 dc.OnMessage(func(msg webrtc.DataChannelMessage) { 211 // Pong 212 if string(msg.Data) != "HELLO WORLD" { 213 t.Errorf("expected message from browser: HELLO WORLD, got: %s", string(msg.Data)) 214 } else { 215 chValid <- struct{}{} 216 } 217 }) 218 }) 219 220 if err := pc.SetRemoteDescription(*sdp); err != nil { 221 t.Fatalf("Failed to set remote description: %v", err) 222 } 223 answer, errAns := pc.CreateAnswer(nil) 224 if errAns != nil { 225 t.Fatalf("Failed to create answer: %v", errAns) 226 } 227 if err := pc.SetLocalDescription(answer); err != nil { 228 t.Fatalf("Failed to set local description: %v", err) 229 } 230 231 answerBytes, errAnsSDP := json.Marshal(answer) 232 if errAnsSDP != nil { 233 t.Fatalf("Failed to marshal SDP: %v", errAnsSDP) 234 } 235 var result string 236 if err := page.RunScript( 237 "pc.setRemoteDescription(JSON.parse(answer))", 238 map[string]interface{}{"answer": string(answerBytes)}, 239 &result, 240 ); err != nil { 241 t.Fatalf("Failed to run script to set SDP: %v", err) 242 } 243 244 select { 245 case <-chStarted: 246 case <-time.After(5 * time.Second): 247 t.Fatal("Timeout") 248 } 249 select { 250 case <-chValid: 251 case <-time.After(5 * time.Second): 252 t.Fatal("Timeout") 253 } 254 }) 255 } 256 } 257 258 type stats []struct { 259 Kind string `json:"kind"` 260 Type string `json:"type"` 261 PacketsReceived int `json:"packetsReceived"` 262 } 263 264 func logParseLoop(ctx context.Context, t *testing.T, page *agouti.Page, chStarted chan struct{}, chSDP chan *webrtc.SessionDescription, chStats chan stats) { 265 for { 266 select { 267 case <-time.After(time.Second): 268 case <-ctx.Done(): 269 return 270 } 271 logs, errLog := page.ReadNewLogs("browser") 272 if errLog != nil { 273 t.Errorf("Failed to read log: %v", errLog) 274 return 275 } 276 for _, log := range logs { 277 k, v, ok := parseLog(log) 278 if !ok { 279 t.Log(log.Message) 280 continue 281 } 282 switch k { 283 case "connection": 284 switch v { 285 case "connected": 286 close(chStarted) 287 case "failed": 288 t.Error("Browser reported connection failed") 289 return 290 } 291 case "sdp": 292 sdp := &webrtc.SessionDescription{} 293 if err := json.Unmarshal([]byte(v), sdp); err != nil { 294 t.Errorf("Failed to unmarshal SDP: %v", err) 295 return 296 } 297 chSDP <- sdp 298 case "stats": 299 if chStats == nil { 300 break 301 } 302 s := &stats{} 303 if err := json.Unmarshal([]byte(v), &s); err != nil { 304 t.Errorf("Failed to parse log: %v", err) 305 break 306 } 307 select { 308 case chStats <- *s: 309 case <-time.After(10 * time.Millisecond): 310 } 311 default: 312 t.Log(log.Message) 313 } 314 } 315 } 316 } 317 318 func parseLog(log agouti.Log) (string, string, bool) { 319 l := strings.SplitN(log.Message, " ", 4) 320 if len(l) != 4 { 321 return "", "", false 322 } 323 k, err1 := strconv.Unquote(l[2]) 324 if err1 != nil { 325 return "", "", false 326 } 327 v, err2 := strconv.Unquote(l[3]) 328 if err2 != nil { 329 return "", "", false 330 } 331 return k, v, true 332 } 333 334 func createTrack(offer webrtc.SessionDescription) (*webrtc.PeerConnection, *webrtc.SessionDescription, *webrtc.TrackLocalStaticSample, error) { 335 pc, errPc := webrtc.NewPeerConnection(webrtc.Configuration{}) 336 if errPc != nil { 337 return nil, nil, nil, errPc 338 } 339 340 track, errTrack := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion") 341 if errTrack != nil { 342 return nil, nil, nil, errTrack 343 } 344 if _, err := pc.AddTrack(track); err != nil { 345 return nil, nil, nil, err 346 } 347 if err := pc.SetRemoteDescription(offer); err != nil { 348 return nil, nil, nil, err 349 } 350 answer, errAns := pc.CreateAnswer(nil) 351 if errAns != nil { 352 return nil, nil, nil, errAns 353 } 354 if err := pc.SetLocalDescription(answer); err != nil { 355 return nil, nil, nil, err 356 } 357 return pc, &answer, track, nil 358 }