Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Page.WaitStable #870

Merged
merged 7 commits into from
May 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
ysmood marked this conversation as resolved.
Show resolved Hide resolved
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.
Fly-Playgroud marked this conversation as resolved.
Show resolved Hide resolved
// `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