123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- package test
- import (
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "path/filepath"
- "strconv"
- "time"
- "unicode/utf8"
- "github.com/browsh-org/browsh/interfacer/src/browsh"
- "github.com/gdamore/tcell"
- "github.com/gdamore/tcell/terminfo"
- ginkgo "github.com/onsi/ginkgo"
- gomega "github.com/onsi/gomega"
- "github.com/spf13/viper"
- )
- var (
- staticFileServerPort = "4444"
- simScreen tcell.SimulationScreen
- startupWait = 60 * time.Second
- perTestTimeout = 2000 * time.Millisecond
- rootDir = browsh.Shell("git rev-parse --show-toplevel")
- testSiteURL = "http://localhost:" + staticFileServerPort
- ti *terminfo.Terminfo
- framesLogFileName string
- frameLogger *slog.Logger
- )
- func init() {
- dir, err := os.Getwd()
- if err != nil {
- panic(err)
- }
- framesLogFileName = fmt.Sprintf(filepath.Join(dir, "frames.log"))
- framesLogFile, err := os.OpenFile(framesLogFileName,
- os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
- 0o644,
- )
- if err != nil {
- panic(err)
- }
- frameLogger = slog.New(slog.NewTextHandler(framesLogFile, nil))
- }
- func initTerm() {
- // The tests check for true colour RGB values. The only downside to forcing true colour
- // in tests is that snapshots of frames with true colour ANSI codes are output to logs.
- // Some people may not have true colour terminals, for example like on Travis, so cat'ing
- // logs may appear corrupt.
- ti, _ = terminfo.LookupTerminfo("xterm-truecolor")
- }
- // GetFrame returns the current Browsh frame's text
- func GetFrame() string {
- var frame, log string
- line := 0
- styleDefault := ti.TParm(ti.SetFgBg, int(tcell.ColorWhite), int(tcell.ColorBlack))
- width, _ := simScreen.Size()
- cells, _, _ := simScreen.GetContents()
- for _, element := range cells {
- line++
- frame += string(element.Runes)
- log += elementColourForTTY(element) + string(element.Runes)
- if line == width {
- frame += "\n"
- log += styleDefault + "\n"
- line = 0
- }
- }
- frameLogger.Info("================================================")
- frameLogger.Info(ginkgo.CurrentGinkgoTestDescription().FullTestText)
- frameLogger.Info("================================================\n")
- return frame
- }
- // Trigger the key definition specified by name
- func triggerUserKeyFor(name string) {
- key := viper.GetStringSlice(name)
- intKey, _ := strconv.Atoi(key[1])
- modifierKey, _ := strconv.Atoi(key[2])
- simScreen.InjectKey(tcell.Key(intKey), []rune(key[0])[0], tcell.ModMask(modifierKey))
- }
- // SpecialKey injects a special key into the TTY. See Tcell's `keys.go` file for all
- // the available special keys.
- func SpecialKey(key tcell.Key) {
- simScreen.InjectKey(key, 0, tcell.ModNone)
- time.Sleep(100 * time.Millisecond)
- }
- // Keyboard types a string of keys into the TTY, as if a user would
- func Keyboard(keys string) {
- for _, char := range keys {
- simScreen.InjectKey(tcell.KeyRune, char, tcell.ModNone)
- time.Sleep(10 * time.Millisecond)
- }
- }
- // SpecialMouse injects a special mouse event into the TTY. See Tcell's `mouse.go` file for all
- // the available special mouse values.
- func SpecialMouse(mouse tcell.ButtonMask) {
- simScreen.InjectMouse(0, 0, mouse, tcell.ModNone)
- time.Sleep(100 * time.Millisecond)
- }
- func waitForNextFrame() {
- // Need to wait so long because the frame rate is currently so slow
- // TODO: Reduce the wait when the FPS is higher
- time.Sleep(250 * time.Millisecond)
- }
- // WaitForText waits for a particular string at particular position in the frame
- func WaitForText(text string, x, y int) {
- var found string
- start := time.Now()
- for time.Since(start) < perTestTimeout {
- found = GetText(x, y, runeCount(text))
- if found == text {
- return
- }
- time.Sleep(100 * time.Millisecond)
- }
- panic("Waiting for '" + text + "' to appear but it didn't")
- }
- // WaitForPageLoad waits for the page to load
- func WaitForPageLoad() {
- sleepUntilPageLoad(perTestTimeout)
- }
- func sleepUntilPageLoad(maxTime time.Duration) {
- start := time.Now()
- time.Sleep(1000 * time.Millisecond)
- for time.Since(start) < maxTime {
- if browsh.CurrentTab != nil {
- if browsh.CurrentTab.PageState == "parsing_complete" {
- time.Sleep(200 * time.Millisecond)
- return
- }
- }
- time.Sleep(50 * time.Millisecond)
- }
- panic("Page didn't load within timeout")
- }
- // GotoURL sends the browsh browser to the specified URL
- func GotoURL(url string) {
- SpecialKey(tcell.KeyCtrlL)
- Keyboard(url)
- SpecialKey(tcell.KeyEnter)
- WaitForPageLoad()
- // TODO: Looking for the URL isn't optimal because it could be the same URL
- // as the previous test.
- gomega.Expect(url).To(BeInFrameAt(0, 1))
- // TODO: hack to work around bug where text sometimes doesn't render on page load.
- // Clicking with the mouse triggers a reparse by the web extension
- mouseClick(3, 6)
- time.Sleep(100 * time.Millisecond)
- mouseClick(3, 6)
- time.Sleep(500 * time.Millisecond)
- }
- func mouseClick(x, y int) {
- simScreen.InjectMouse(x, y, 1, tcell.ModNone)
- simScreen.InjectMouse(x, y, 0, tcell.ModNone)
- }
- func elementColourForTTY(element tcell.SimCell) string {
- var fg, bg tcell.Color
- fg, bg, _ = element.Style.Decompose()
- r1, g1, b1 := fg.RGB()
- r2, g2, b2 := bg.RGB()
- return ti.TParm(ti.SetFgBgRGB,
- int(r1), int(g1), int(b1),
- int(r2), int(g2), int(b2))
- }
- // GetText retruns an individual piece of a frame
- func GetText(x, y, length int) string {
- var text string
- frame := []rune(GetFrame())
- width, _ := simScreen.Size()
- index := ((width + 1) * y) + x
- for {
- text += string(frame[index])
- index++
- if runeCount(text) == length {
- break
- }
- }
- return text
- }
- // GetFgColour returns the foreground colour of a single cell
- func GetFgColour(x, y int) [3]int32 {
- GetFrame()
- cells, _, _ := simScreen.GetContents()
- width, _ := simScreen.Size()
- index := (width * y) + x
- fg, _, _ := cells[index].Style.Decompose()
- r1, g1, b1 := fg.RGB()
- return [3]int32{r1, g1, b1}
- }
- // GetBgColour returns the background colour of a single cell
- func GetBgColour(x, y int) [3]int32 {
- GetFrame()
- cells, _, _ := simScreen.GetContents()
- width, _ := simScreen.Size()
- index := (width * y) + x
- _, bg, _ := cells[index].Style.Decompose()
- r1, g1, b1 := bg.RGB()
- return [3]int32{r1, g1, b1}
- }
- func ensureOnlyOneTab() {
- if len(browsh.Tabs) > 1 {
- SpecialKey(tcell.KeyCtrlW)
- }
- }
- func startStaticFileServer() {
- serverMux := http.NewServeMux()
- serverMux.Handle("/", http.FileServer(http.Dir(rootDir+"/interfacer/test/sites")))
- http.ListenAndServe(":"+staticFileServerPort, serverMux)
- }
- func initBrowsh() {
- browsh.IsTesting = true
- simScreen = tcell.NewSimulationScreen("UTF-8")
- browsh.Initialise()
- }
- func stopFirefox() {
- slog.Info("Attempting to kill all firefox processes")
- browsh.IsConnectedToWebExtension = false
- browsh.Shell(rootDir + "/webext/contrib/firefoxheadless.sh kill")
- time.Sleep(500 * time.Millisecond)
- }
- func runeCount(text string) int {
- return utf8.RuneCountInString(text)
- }
- var _ = ginkgo.BeforeEach(func() {
- slog.Info("Attempting to restart WER Firefox...")
- stopFirefox()
- browsh.ResetTabs()
- browsh.StartFirefox()
- sleepUntilPageLoad(startupWait)
- browsh.IsMonochromeMode = false
- slog.Info("\n---------")
- slog.Info(ginkgo.CurrentGinkgoTestDescription().FullTestText)
- slog.Info("---------")
- })
- var _ = ginkgo.BeforeSuite(func() {
- os.Truncate(framesLogFileName, 0)
- initTerm()
- initBrowsh()
- stopFirefox()
- go startStaticFileServer()
- go browsh.TTYStart(simScreen)
- // Firefox seems to take longer to die after its first run
- time.Sleep(500 * time.Millisecond)
- stopFirefox()
- time.Sleep(5000 * time.Millisecond)
- })
- var _ = ginkgo.AfterSuite(func() {
- stopFirefox()
- })
|