Skip to content

Commit

Permalink
feat: add Page.WaitStable (#870)
Browse files Browse the repository at this point in the history
* feat: add Page.WaitStable

* 1.add Page.MustWaitStable
2.mod unit test

* add word
add word "Rects"

* Improve code coverage

* pretty html

* pretty html
  • Loading branch information
Fly-Playgroud committed May 14, 2023
1 parent f76a7b2 commit 1336c8c
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 1 deletion.
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@
"wsutil",
"xlink",
"XVFB",
"ysmood"
"ysmood",
"Rects"
],
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
Expand Down
90 changes: 90 additions & 0 deletions fixtures/page-wait-stable.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<title>PageWaitStable</title>
<style>
/* 进度条的样式 */
progress[value] {
display: block;
width: 100%;
margin-top: 20px;
-webkit-appearance: none;
appearance: none;
height: 10px;
background-color: #ddd;
}

progress[value]::-webkit-progress-bar {
background-color: #ddd;
}

progress[value]::-webkit-progress-value {
background-color: #0078ff;
}

/* loading 动画的样式 */
.loading {
display: block;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
}
</style>
</head>

<body>
<!-- 进度条 -->
<progress id="progressBar" value="0" max="100"></progress>

<!-- loading 动画 -->
<div id="loading" class="loading">
<img src="path-to-loading-gif" alt="loading" />
</div>

<!-- Your other HTML content here -->

<!-- 页面加载完成时的弹窗 -->
<div
id="popup"
style="
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 2px 2px 10px #888;
"
>
<h1 style="text-align: center">Page loading and rendering complete</h1>
</div>

<script>
/* 页面加载时触发的函数 */
window.onload = function () {
/* 获取进度条和 loading 动画元素 */
var progressBar = document.getElementById('progressBar')
var loading = document.getElementById('loading')
var popup = document.getElementById('popup')
var progress = 0
/* 定时器,每 50ms 触发一次 */
var timer = setInterval(function () {
/* 如果进度条已满,则隐藏 loading 动画,并显示弹窗 */
if (progress === 100) {
clearInterval(timer)
loading.style.display = 'none'
popup.style.display = 'block'
} else {
/* 否则,增加进度,更新进度条 */
progress += 1
progressBar.value = progress
}
}, 50)
}
</script>
</body>
</html>
13 changes: 13 additions & 0 deletions must.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,13 @@ func (p *Page) MustScreenshot(toFile ...string) []byte {
return bin
}

// MustCaptureDOMSnapshot is similar to CaptureDOMSnapshot.
func (p *Page) MustCaptureDOMSnapshot() (domSnapshot *proto.DOMSnapshotCaptureSnapshotResult) {
domSnapshot, err := p.CaptureDOMSnapshot()
p.e(err)
return domSnapshot
}

// MustScreenshotFullPage is similar to ScreenshotFullPage.
// If the toFile is "", it Page.will save output to "tmp/screenshots" folder, time as the file name.
func (p *Page) MustScreenshotFullPage(toFile ...string) []byte {
Expand Down Expand Up @@ -412,6 +419,12 @@ func (p *Page) MustWaitIdle() *Page {
return p
}

// MustWaitStable is similar to Page.WaitStable
func (p *Page) MustWaitStable() *Page {
p.e(p.WaitStable(800*time.Millisecond, 1))
return p
}

// MustWaitLoad is similar to Page.WaitLoad
func (p *Page) MustWaitLoad() *Page {
p.e(p.WaitLoad())
Expand Down
72 changes: 72 additions & 0 deletions page.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/go-rod/rod/lib/proto"
"github.com/go-rod/rod/lib/utils"
"github.com/ysmood/goob"
"github.com/ysmood/got/lib/lcs"
"github.com/ysmood/gson"
)

Expand Down Expand Up @@ -442,6 +443,30 @@ func (p *Page) Screenshot(fullPage bool, req *proto.PageCaptureScreenshot) ([]by
return shot.Data, nil
}

// CaptureDOMSnapshot Returns a document snapshot, including the full DOM tree of the root node
// (including iframes, template contents, and imported documents) in a flattened array,
// as well as layout and white-listed computed style information for the nodes.
// Shadow DOM in the returned DOM tree is flattened.
// `Documents` The nodes in the DOM tree. The DOMNode at index 0 corresponds to the root document.
// `Strings` Shared string table that all string properties refer to with indexes.
// Normally use `Strings` is enough.
func (p *Page) CaptureDOMSnapshot() (domSnapshot *proto.DOMSnapshotCaptureSnapshotResult, err error) {
_ = proto.DOMSnapshotEnable{}.Call(p)

snapshot, err := proto.DOMSnapshotCaptureSnapshot{
ComputedStyles: []string{},
IncludePaintOrder: true,
IncludeDOMRects: true,
IncludeBlendedBackgroundColors: true,
IncludeTextColorOpacities: true,
}.Call(p)

if err != nil {
return nil, err
}
return snapshot, nil
}

// PDF prints page as PDF
func (p *Page) PDF(req *proto.PagePrintToPDF) (*StreamReader, error) {
req.TransferMode = proto.PagePrintToPDFTransferModeReturnAsStream
Expand Down Expand Up @@ -584,6 +609,53 @@ func (p *Page) WaitRequestIdle(d time.Duration, includes, excludes []string) fun
}
}

// WaitStable like "Element.WaitStable". WaitStable polling the changes
// of the DOM tree in `d` duration,until the similarity equal or more than simThreshold.
// `simThreshold` is the similarity threshold,it's scope in [0,1].
// Be careful,d is not the max wait timeout, it's the least stable time.
// If you want to set a timeout you can use the "Page.Timeout" function.
func (p *Page) WaitStable(d time.Duration, similarity float32) error {
err := p.WaitLoad()
if err != nil {
return err
}

defer p.tryTrace(TraceTypeWait, "stable")

domSnapshot, err := p.CaptureDOMSnapshot()
if err != nil {
return err
}

t := time.NewTicker(d)
defer t.Stop()

for {
select {
case <-t.C:
case <-p.ctx.Done():
return p.ctx.Err()
}

currentDomSnapshot, err := p.CaptureDOMSnapshot()
if err != nil {
return err
}

xs := lcs.NewWords(domSnapshot.Strings)
ys := lcs.NewWords(currentDomSnapshot.Strings)
diff := xs.YadLCS(p.ctx, ys)

sim := float32(len(diff)) / float32(len(ys))
if sim >= similarity {
break
}

domSnapshot = currentDomSnapshot
}
return nil
}

// WaitIdle waits until the next window.requestIdleCallback is called.
func (p *Page) WaitIdle(timeout time.Duration) (err error) {
_, err = p.Evaluate(evalHelper(js.WaitIdle, timeout.Milliseconds()).ByPromise())
Expand Down
50 changes: 50 additions & 0 deletions page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,56 @@ func TestPageWaitRequestIdle(t *testing.T) {
})
}

func TestPageCaptureDOMSnapshot(t *testing.T) {
g := setup(t)

p := g.page.MustNavigate(g.srcFile("fixtures/click.html"))
domSnapshot := p.MustCaptureDOMSnapshot()
g.Is(domSnapshot.Strings, []string{})

timeOutPage := p.Timeout(1 * time.Second)
utils.Sleep(1)
snapshot, err := timeOutPage.CaptureDOMSnapshot()
g.Is(err, context.DeadlineExceeded)
g.Nil(snapshot)

}

func TestPageWaitStable(t *testing.T) {
g := setup(t)

// for waitLoad failed
g.Panic(func() {
g.mc.stubErr(1, proto.RuntimeCallFunctionOn{})
g.page.MustWaitStable()
})

p := g.page.MustNavigate(g.srcFile("fixtures/page-wait-stable.html"))
// wait for p loading and rending complete
p.MustWaitStable()

// for waitStable timeout
timeOutPage := p.Timeout(1 * time.Second)
err := timeOutPage.WaitStable(2*time.Second, 1)
g.Is(err, context.DeadlineExceeded)

{
g.Panic(func() {
p := g.page.MustNavigate(g.srcFile("fixtures/page-wait-stable.html"))
g.mc.stubErr(1, proto.DOMSnapshotCaptureSnapshot{})
p.MustWaitStable()
})
}

{
g.Panic(func() {
p := g.page.MustNavigate(g.srcFile("fixtures/page-wait-stable.html"))
g.mc.stubErr(2, proto.DOMSnapshotCaptureSnapshot{})
p.MustWaitStable()
})
}
}

func TestPageWaitIdle(t *testing.T) {
g := setup(t)

Expand Down

0 comments on commit 1336c8c

Please sign in to comment.