snapshot of https://github.com/pion/webrtc.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
REUSE Compliance Check / lint (push) Failing after 0s
Browser E2E / Test (push) Failing after 3s
Test / test (1.24) (push) Failing after 0s
Test / test-i386 (1.25) (push) Failing after 0s
Test / test-i386 (1.24) (push) Failing after 0s
Test / test-windows (1.25) (push) Failing after 0s
Test / test-windows (1.24) (push) Failing after 0s
Test / test (1.25) (push) Failing after 0s
Examples Tests / Test (push) Failing after 3s
Test / test-macos (1.25) (push) Failing after 0s
Test / test-macos (1.24) (push) Failing after 0s
Go mod tidy / tidy (push) Failing after 0s
Test / test-wasm (push) Failing after 0s
CodeQL / analyze (push) Failing after 1s
Fuzz / fuzz (push) Failing after 0s

This commit is contained in:
upstream-mirror
2026-09-15 20:06:07 +05:00
commit 8acb1a3a29
394 changed files with 78746 additions and 0 deletions

16
e2e/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
FROM golang:1.27-alpine
RUN apk add --no-cache \
chromium \
chromium-chromedriver \
git
ENV CGO_ENABLED=0
COPY . /go/src/github.com/pion/webrtc
WORKDIR /go/src/github.com/pion/webrtc/e2e
CMD ["go", "test", "-tags=e2e", "-v", "."]

358
e2e/e2e_test.go Normal file
View File

@@ -0,0 +1,358 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build e2e
// +build e2e
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media"
"github.com/sclevine/agouti"
)
var silentOpusFrame = []byte{0xf8, 0xff, 0xfe} // 20ms, 8kHz, mono
var drivers = map[string]func() *agouti.WebDriver{
"Chrome": func() *agouti.WebDriver {
return agouti.ChromeDriver(
agouti.ChromeOptions("args", []string{
"--headless",
"--disable-gpu",
"--no-sandbox",
}),
agouti.Desired(agouti.Capabilities{
"loggingPrefs": map[string]string{
"browser": "INFO",
},
}),
)
},
}
func TestE2E_Audio(t *testing.T) {
for name, d := range drivers {
driver := d()
t.Run(name, func(t *testing.T) {
if err := driver.Start(); err != nil {
t.Fatalf("Failed to start WebDriver: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer func() {
cancel()
time.Sleep(50 * time.Millisecond)
_ = driver.Stop()
}()
page, errPage := driver.NewPage()
if errPage != nil {
t.Fatalf("Failed to open page: %v", errPage)
}
if err := page.SetPageLoad(1000); err != nil {
t.Fatalf("Failed to load page: %v", err)
}
if err := page.SetImplicitWait(1000); err != nil {
t.Fatalf("Failed to set wait: %v", err)
}
chStarted := make(chan struct{})
chSDP := make(chan *webrtc.SessionDescription)
chStats := make(chan stats)
go logParseLoop(ctx, t, page, chStarted, chSDP, chStats)
pwd, errPwd := os.Getwd()
if errPwd != nil {
t.Fatalf("Failed to get working directory: %v", errPwd)
}
if err := page.Navigate(
fmt.Sprintf("file://%s/test.html", pwd),
); err != nil {
t.Fatalf("Failed to navigate: %v", err)
}
sdp := <-chSDP
pc, answer, track, errTrack := createTrack(*sdp)
if errTrack != nil {
t.Fatalf("Failed to create track: %v", errTrack)
}
defer func() {
_ = pc.Close()
}()
answerBytes, errAnsSDP := json.Marshal(answer)
if errAnsSDP != nil {
t.Fatalf("Failed to marshal SDP: %v", errAnsSDP)
}
var result string
if err := page.RunScript(
"pc.setRemoteDescription(JSON.parse(answer))",
map[string]any{"answer": string(answerBytes)},
&result,
); err != nil {
t.Fatalf("Failed to run script to set SDP: %v", err)
}
go func() {
for {
if err := track.WriteSample(
media.Sample{Data: silentOpusFrame, Duration: time.Millisecond * 20},
); err != nil {
t.Errorf("Failed to WriteSample: %v", err)
return
}
select {
case <-time.After(20 * time.Millisecond):
case <-ctx.Done():
return
}
}
}()
select {
case <-chStarted:
case <-time.After(5 * time.Second):
t.Fatal("Timeout")
}
<-chStats
var packetReceived [2]int
for i := 0; i < 2; i++ {
select {
case stat := <-chStats:
for _, s := range stat {
if s.Type != "inbound-rtp" {
continue
}
if s.Kind != "audio" {
t.Errorf("Unused track stat received: %+v", s)
continue
}
packetReceived[i] = s.PacketsReceived
}
case <-time.After(5 * time.Second):
t.Fatal("Timeout")
}
}
packetsPerSecond := packetReceived[1] - packetReceived[0]
if packetsPerSecond < 45 || 55 < packetsPerSecond {
t.Errorf("Number of OPUS packets is expected to be: 50/second, got: %d/second", packetsPerSecond)
}
})
}
}
func TestE2E_DataChannel(t *testing.T) {
for name, d := range drivers {
driver := d()
t.Run(name, func(t *testing.T) {
if err := driver.Start(); err != nil {
t.Fatalf("Failed to start WebDriver: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer func() {
cancel()
time.Sleep(50 * time.Millisecond)
_ = driver.Stop()
}()
page, errPage := driver.NewPage()
if errPage != nil {
t.Fatalf("Failed to open page: %v", errPage)
}
if err := page.SetPageLoad(1000); err != nil {
t.Fatalf("Failed to load page: %v", err)
}
if err := page.SetImplicitWait(1000); err != nil {
t.Fatalf("Failed to set wait: %v", err)
}
chStarted := make(chan struct{})
chSDP := make(chan *webrtc.SessionDescription)
go logParseLoop(ctx, t, page, chStarted, chSDP, nil)
pwd, errPwd := os.Getwd()
if errPwd != nil {
t.Fatalf("Failed to get working directory: %v", errPwd)
}
if err := page.Navigate(
fmt.Sprintf("file://%s/test.html", pwd),
); err != nil {
t.Fatalf("Failed to navigate: %v", err)
}
sdp := <-chSDP
pc, errPc := webrtc.NewPeerConnection(webrtc.Configuration{})
if errPc != nil {
t.Fatalf("Failed to create peer connection: %v", errPc)
}
defer func() {
_ = pc.Close()
}()
chValid := make(chan struct{})
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
dc.OnOpen(func() {
// Ping
if err := dc.SendText("hello world"); err != nil {
t.Errorf("Failed to send data: %v", err)
}
})
dc.OnMessage(func(msg webrtc.DataChannelMessage) {
// Pong
if string(msg.Data) != "HELLO WORLD" {
t.Errorf("expected message from browser: HELLO WORLD, got: %s", string(msg.Data))
} else {
chValid <- struct{}{}
}
})
})
if err := pc.SetRemoteDescription(*sdp); err != nil {
t.Fatalf("Failed to set remote description: %v", err)
}
answer, errAns := pc.CreateAnswer(nil)
if errAns != nil {
t.Fatalf("Failed to create answer: %v", errAns)
}
if err := pc.SetLocalDescription(answer); err != nil {
t.Fatalf("Failed to set local description: %v", err)
}
answerBytes, errAnsSDP := json.Marshal(answer)
if errAnsSDP != nil {
t.Fatalf("Failed to marshal SDP: %v", errAnsSDP)
}
var result string
if err := page.RunScript(
"pc.setRemoteDescription(JSON.parse(answer))",
map[string]any{"answer": string(answerBytes)},
&result,
); err != nil {
t.Fatalf("Failed to run script to set SDP: %v", err)
}
select {
case <-chStarted:
case <-time.After(5 * time.Second):
t.Fatal("Timeout")
}
select {
case <-chValid:
case <-time.After(5 * time.Second):
t.Fatal("Timeout")
}
})
}
}
type stats []struct {
Kind string `json:"kind"`
Type string `json:"type"`
PacketsReceived int `json:"packetsReceived"`
}
func logParseLoop(ctx context.Context, t *testing.T, page *agouti.Page, chStarted chan struct{}, chSDP chan *webrtc.SessionDescription, chStats chan stats) {
for {
select {
case <-time.After(time.Second):
case <-ctx.Done():
return
}
logs, errLog := page.ReadNewLogs("browser")
if errLog != nil {
t.Errorf("Failed to read log: %v", errLog)
return
}
for _, log := range logs {
k, v, ok := parseLog(log)
if !ok {
t.Log(log.Message)
continue
}
switch k {
case "connection":
switch v {
case "connected":
close(chStarted)
case "failed":
t.Error("Browser reported connection failed")
return
}
case "sdp":
sdp := &webrtc.SessionDescription{}
if err := json.Unmarshal([]byte(v), sdp); err != nil {
t.Errorf("Failed to unmarshal SDP: %v", err)
return
}
chSDP <- sdp
case "stats":
if chStats == nil {
break
}
s := &stats{}
if err := json.Unmarshal([]byte(v), &s); err != nil {
t.Errorf("Failed to parse log: %v", err)
break
}
select {
case chStats <- *s:
case <-time.After(10 * time.Millisecond):
}
default:
t.Log(log.Message)
}
}
}
}
func parseLog(log agouti.Log) (string, string, bool) {
l := strings.SplitN(log.Message, " ", 4)
if len(l) != 4 {
return "", "", false
}
k, err1 := strconv.Unquote(l[2])
if err1 != nil {
return "", "", false
}
v, err2 := strconv.Unquote(l[3])
if err2 != nil {
return "", "", false
}
return k, v, true
}
func createTrack(offer webrtc.SessionDescription) (*webrtc.PeerConnection, *webrtc.SessionDescription, *webrtc.TrackLocalStaticSample, error) {
pc, errPc := webrtc.NewPeerConnection(webrtc.Configuration{})
if errPc != nil {
return nil, nil, nil, errPc
}
track, errTrack := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion")
if errTrack != nil {
return nil, nil, nil, errTrack
}
if _, err := pc.AddTrack(track); err != nil {
return nil, nil, nil, err
}
if err := pc.SetRemoteDescription(offer); err != nil {
return nil, nil, nil, err
}
answer, errAns := pc.CreateAnswer(nil)
if errAns != nil {
return nil, nil, nil, errAns
}
if err := pc.SetLocalDescription(answer); err != nil {
return nil, nil, nil, err
}
return pc, &answer, track, nil
}

45
e2e/test.html Normal file
View File

@@ -0,0 +1,45 @@
<!--
SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<div id="media"></div>
<script>
const pc = new RTCPeerConnection()
pc.ontrack = event => {
if (event.track.kind === 'audio') {
var el = document.createElement(event.track.kind)
el.srcObject = new MediaStream(event.streams[0].getAudioTracks())
document.getElementById('media').appendChild(el)
}
}
pc.oniceconnectionstatechange = event => {
console.log("connection", pc.iceConnectionState)
if (pc.iceConnectionState == 'connected') {
setInterval(statsReport, 1000)
}
}
pc.onicecandidate = event => {
if (event.candidate === null) {
console.log("sdp", JSON.stringify(pc.localDescription))
}
}
pc.addTransceiver('audio', {'direction': 'recvonly'})
const dc = pc.createDataChannel("upper")
dc.onmessage = event => {
dc.send(event.data.toUpperCase())
}
pc.createOffer().then(d => pc.setLocalDescription(d)).catch(console.log)
const statsReport = async () => {
const stats = await pc.getStats()
var data = []
await stats.forEach(item => {
data.push(item)
})
console.log("stats", JSON.stringify(data))
}
</script>