diff --git a/Makefile b/Makefile
index 9b52d01..f9e2116 100644
--- a/Makefile
+++ b/Makefile
@@ -3,8 +3,8 @@ help: ## Show this help
@grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/:.*##/:##/' | column -t -s '##'
.PHONY: test
-test: ## Runs the test
- @xvfb-run go test ./... $(ARGS)
+test:
+ @xvfb-run go test ./...
.PHONY: pprof
pprof: ## Runs pprof server for 'cpu.out'
diff --git a/README.md b/README.md
index 01a3699..2483223 100644
--- a/README.md
+++ b/README.md
@@ -91,28 +91,43 @@ func TestGameUI(t *testing.T) {
}
```
-The output of this test (that fails) is the following:
+An output could be for example
```
---- FAIL: TestGameUI (7.52s)
- ebitest_test.go:28:
- Error Trace: /home/xescugc/repos/ebitest/ebitest.go:113
- /home/xescugc/repos/ebitest/ebitest_test.go:28
+--- FAIL: TestGameButton (9.34s)
+ ebitest_test.go:39:
+ Error Trace: ebitest.go:150
+ ebitest_test.go:39
+ Error: selector found
+ image at: ebitest/_ebitest_dump/019b2e81-8d9a-7bb7-ba16-639756d11e58.png
+ Test: TestGameButton
+ ebitest_test.go:43:
+ Error Trace: ebitest.go:126
+ ebitest_test.go:43
Error: selector not found
- image at: _ebitest_dump/019b1537-1c60-7041-ad54-0297ea4b0eef.png
- Test: TestGameUI
+ image at: ebitest/_ebitest_dump/019b2e81-966e-7715-8c5f-6a6acc92720c.png
+ Test: TestGameButton
FAIL
-FAIL github.com/xescugc/ebitest 7.578s
+FAIL github.com/xescugc/ebitest 9.393s
FAIL
-make: *** [Makefile:7: test] Error 1
+make: *** [Makefile:7: test] Error
```
-And if you open the `_ebitest_dump/019b1537-1c60-7041-ad54-0297ea4b0eef.png` (on the current path) you see
+And then you have the `image at: ebitest/_ebitest_dump/019b2e81-8d9a-7bb7-ba16-639756d11e58.png` that expects to not find something, and it finds
+it and reports the image with the highlight of what was found. At the top right you can see what was looking for.
-
+
+And the `image at: ebitest/_ebitest_dump/019b2e81-966e-7715-8c5f-6a6acc92720c.png` that expect to find something that was not found. At the top right
+you can see what was looking for.
+
+
+
+
+
+
## Run it on a CI
If the CI has low resources (like GitHub Actions) it'll most likely fail (check `Known issues#2`) but you
@@ -133,6 +148,11 @@ so an expectation may randomly fail.
I kind of fixed it (100 consecutive test pass) using a custom [PingPong](./ping_pong.go) and [TicTacToe](./tic_tac_toe.go) that basically forces a context switch and synchronizes Input+Game.Update+Game.Draw but it still fails in low resource like GitHub [Actions](https://github.com/xescugc/ebitest/actions) for example.
+3/ Size of the screen
+
+By default the screen is of `640x480` if using `xvfb`. To make it bigger you can directly increase the size with `ebiten.SetWindowSize(750, 750)`, though setting big sizes
+I've seen it causes some issues that the click are not where they are expected to be and are a bit off and miss which causes errors.
+
## Plans
* Add more helpers for assertions (like animations)
diff --git a/docs/error_image.png b/docs/error_image.png
deleted file mode 100644
index 6033dba..0000000
Binary files a/docs/error_image.png and /dev/null differ
diff --git a/docs/should.png b/docs/should.png
new file mode 100644
index 0000000..680da7c
Binary files /dev/null and b/docs/should.png differ
diff --git a/docs/should_not.png b/docs/should_not.png
new file mode 100644
index 0000000..5b40420
Binary files /dev/null and b/docs/should_not.png differ
diff --git a/ebitest.go b/ebitest.go
index ee58304..e0248aa 100644
--- a/ebitest.go
+++ b/ebitest.go
@@ -24,6 +24,10 @@ const (
findAllSelectors = true
)
+var (
+ emptyRec image.Rectangle
+)
+
type Ebitest struct {
game *Game
PingPong *PingPong
@@ -116,7 +120,7 @@ func (e *Ebitest) Should(t *testing.T, s interface{}) (*Selector, bool) {
if !ok {
msg := "selector not found"
if e.options.dumpErrorImages {
- p := dumpErrorImages(sc, sel.Image())
+ p := dumpErrorImages(sc, sel)
msg += "\nimage at: " + p
}
assert.Fail(t, msg)
@@ -140,7 +144,7 @@ func (e *Ebitest) ShouldNot(t *testing.T, s interface{}) bool {
msg := "selector found"
if e.options.dumpErrorImages {
- p := dumpErrorImages(sc, sel.Image())
+ p := dumpErrorImages(sc, sel)
msg += "\nimage at: " + p
}
assert.Fail(t, msg)
@@ -159,7 +163,7 @@ func (e *Ebitest) Must(t *testing.T, s interface{}) *Selector {
if !ok {
msg := "selector not found"
if e.options.dumpErrorImages {
- p := dumpErrorImages(sc, sel.Image())
+ p := dumpErrorImages(sc, sel)
msg += "\nimage at: " + p
}
require.Fail(t, msg)
@@ -184,7 +188,7 @@ func (e *Ebitest) MustNot(t *testing.T, s interface{}) {
msg := "selector found"
if e.options.dumpErrorImages {
- p := dumpErrorImages(sc, sel.Image())
+ p := dumpErrorImages(sc, sel)
msg += "\nimage at: " + p
}
require.Fail(t, msg)
@@ -193,7 +197,9 @@ func (e *Ebitest) MustNot(t *testing.T, s interface{}) {
// GetAll returns all the repeated instances of s or none if nothing is found
func (e *Ebitest) GetAll(s interface{}) []*Selector {
sc := e.game.GetScreen()
- return e.findSelectors(sc, s, findAllSelectors)
+ sels, _ := e.findSelectors(sc, s, findAllSelectors)
+
+ return sels
}
// KeyTap taps all the keys at once
@@ -222,15 +228,15 @@ func (e *Ebitest) getSelector(s interface{}) *Selector {
// findSelector returns a Selector from ss if found. `all` will basically mean it'll return all of them
func (e *Ebitest) findSelector(sc image.Image, ss interface{}) (*Selector, bool) {
- sels := e.findSelectors(sc, ss, !findAllSelectors)
+ sels, sel := e.findSelectors(sc, ss, !findAllSelectors)
if len(sels) == 0 {
- return nil, false
+ return sel, false
}
return sels[0], true
}
// findSelector returns a Selector from ss if found. `all` will basically mean it'll return all of them
-func (e *Ebitest) findSelectors(sc image.Image, ss interface{}, all bool) []*Selector {
+func (e *Ebitest) findSelectors(sc image.Image, ss interface{}, all bool) ([]*Selector, *Selector) {
selectors := make([]*Selector, 0)
bsel := e.getSelector(ss)
@@ -247,18 +253,50 @@ func (e *Ebitest) findSelectors(sc image.Image, ss interface{}, all bool) []*Sel
sel.PingPong = e.PingPong
selectors = append(selectors, sel)
if !all {
- return selectors
+ return selectors, bsel
}
}
}
}
- return selectors
+ return selectors, bsel
+}
+
+// hasImageAt checks if the image sub is image i at the ix, iy
+func hasImageAt(i, sub image.Image, ix, iy int) bool {
+ sx, sy := sub.Bounds().Dx(), sub.Bounds().Dy()
+ for x := range sx {
+ for y := range sy {
+ sc := sub.At(x, y)
+ ic := i.At(ix+x, iy+y)
+
+ nsc := sc.(color.NRGBA)
+ // If the source it's transparent we ignore it
+ // we want to only compare colors so we consider
+ // it as good
+ if nsc.A != 255 {
+ continue
+ }
+
+ if !equalColors(sc, ic) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// equalColors checks if c1 and c2 have the same RGB
+func equalColors(c1, c2 color.Color) bool {
+ r1, g1, b1, _ := c1.RGBA()
+ r2, g2, b2, _ := c2.RGBA()
+ return r1 == r2 && g1 == g2 && b1 == b2
}
// dumpErrorImages dumps a composition of the 2 images into 1 so it displays
// what was checked
-func dumpErrorImages(s, i image.Image) string {
+func dumpErrorImages(s image.Image, sel *Selector) string {
+ i := sel.Image()
sb := s.Bounds()
ib := i.Bounds()
x := sb.Dx() + ib.Dx()
@@ -268,6 +306,10 @@ func dumpErrorImages(s, i image.Image) string {
draw.Draw(img, sb, s, image.Point{}, draw.Over)
draw.Draw(img, image.Rect(sb.Dx(), 0, x, ib.Dy()), i, image.Point{}, draw.Over)
+ if sel.Rec() != emptyRec {
+ drawRectangle(img, sel.Rec(), 2)
+ }
+
u, _ := uuid.NewV7()
ip := filepath.Join(baseDumpFoler, u.String()+".png")
@@ -277,6 +319,24 @@ func dumpErrorImages(s, i image.Image) string {
return filepath.Join(wd, ip)
}
+// drawRectangle will draw in the image(img) the rectangel(rec) with thiknes
+func drawRectangle(img *image.RGBA, rec image.Rectangle, thickness int) {
+ col := color.RGBA{255, 0, 0, 255}
+
+ for t := 0; t < thickness; t++ {
+ // draw horizontal lines
+ for x := rec.Min.X; x <= rec.Max.X; x++ {
+ img.Set(x, rec.Min.Y+t, col)
+ img.Set(x, rec.Max.Y-t, col)
+ }
+ // draw vertical lines
+ for y := rec.Min.Y; y <= rec.Max.Y; y++ {
+ img.Set(rec.Min.X+t, y, col)
+ img.Set(rec.Max.X-t, y, col)
+ }
+ }
+}
+
// writeImage writes on the path the image i
func writeImage(path string, i image.Image) {
f, err := os.Create(path)
@@ -289,53 +349,3 @@ func writeImage(path string, i image.Image) {
log.Fatal(err)
}
}
-
-// hasImageAt checks if the image sub is image i at the ix, iy
-func hasImageAt(i, sub image.Image, ix, iy int) bool {
- sx, sy := sub.Bounds().Dx(), sub.Bounds().Dy()
- for x := range sx {
- for y := range sy {
- ic := toNRGBA(i.At(ix+x, iy+y))
- sc := toNRGBA(sub.At(x, y))
- sr, sg, sb, sa := sc.RGBA()
-
- // If the source it's transparent we ignore it
- // we want to only compare colors so we consider
- // it as good
- if sa == 0 || (sr == 0 && sg == 0 && sb == 0) {
- continue
- }
-
- if !equalColors(sc, ic) {
- return false
- }
- }
- }
- return true
-}
-
-// equalColors checks if c1 and c2 have the same RGB
-func equalColors(c1, c2 color.Color) bool {
- r1, g1, b1, _ := c1.RGBA()
- r2, g2, b2, _ := c2.RGBA()
- return r1 == r2 && g1 == g2 && b1 == b2
-}
-
-// toNRGBA convers a pre-multiplied alpha color to a non pre-multiplied alpha one
-func toNRGBA(c color.Color) color.Color {
- r, g, b, a := c.RGBA()
- if a == 0 {
- return color.NRGBA{0, 0, 0, 0}
- }
-
- // Since color.Color is alpha pre-multiplied, we need to divide the
- // RGB values by alpha again in order to get back the original RGB.
- r *= 0xffff
- r /= a
- g *= 0xffff
- g /= a
- b *= 0xffff
- b /= a
-
- return color.NRGBA{uint8(r / 65535), uint8(g / 65535), uint8(b / 65535), 255}
-}
diff --git a/ebitest_test.go b/ebitest_test.go
index bff47a4..e55bda3 100644
--- a/ebitest_test.go
+++ b/ebitest_test.go
@@ -32,10 +32,16 @@ func TestGameButton(t *testing.T) {
text1_2 := "Click Me 2"
text2 := "Clicked Me"
- t1s, _ := et.Should(t, text1)
+ t1s := et.Must(t, text1)
t1_2s, _ := et.Should(t, text1_2)
+
+ // Fails
+ et.ShouldNot(t, text1_2)
et.ShouldNot(t, text2)
+ // Fails
+ et.Should(t, text2)
+
t1s.Click()
et.Should(t, text1_2)
@@ -47,7 +53,6 @@ func TestGameButton(t *testing.T) {
et.ShouldNot(t, text1_2)
assert.Len(t, et.GetAll(text2), 2)
- //et.KeyTap(ebiten.KeyShift, ebiten.KeyI)
et.KeyTap(ebiten.KeyI, ebiten.KeyShift)
assert.True(t, g.ClickedShiftI)
}
diff --git a/utils.go b/utils.go
index 2aa24eb..00e099a 100644
--- a/utils.go
+++ b/utils.go
@@ -9,7 +9,7 @@ import (
// ebitenImageToImage converts an ebiten.Image to an image.Image
func ebitenImageToImage(ei *ebiten.Image) image.Image {
b := ei.Bounds()
- img := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy()))
+ img := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
ix, iy := ei.Bounds().Dx(), ei.Bounds().Dy()
for x := range ix {