e2e_test.go 8.6 KB

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