feat: add PromoteToInteractive and DemoteToDocument for mid-session page transfer
Allow transferring ownership of a Playwright page between Document and InteractiveBrowser modes without tearing down the browser. This enables handing a live page to a human (e.g. for captcha solving) and resuming scraping on the same page afterward. Closes #76 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
65
promote.go
Normal file
65
promote.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package extractor
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrNotPromotable is returned when a Document cannot be promoted to an InteractiveBrowser.
|
||||
// This happens when the Document is not backed by a Playwright page (e.g. a mock or custom implementation).
|
||||
var ErrNotPromotable = errors.New("document is not promotable to InteractiveBrowser")
|
||||
|
||||
// ErrNotDemotable is returned when an InteractiveBrowser cannot be demoted to a Document.
|
||||
// This happens when the InteractiveBrowser is not backed by a Playwright page.
|
||||
var ErrNotDemotable = errors.New("interactive browser is not demotable to Document")
|
||||
|
||||
// ErrAlreadyDetached is returned when attempting to promote or demote an object that has
|
||||
// already been transferred. Each Document or InteractiveBrowser can only be promoted/demoted once.
|
||||
var ErrAlreadyDetached = errors.New("already detached")
|
||||
|
||||
// PromoteToInteractive transfers ownership of the underlying Playwright page from a Document
|
||||
// to a new InteractiveBrowser. After promotion, the Document's Close method becomes a no-op
|
||||
// (the page is now owned by the returned InteractiveBrowser).
|
||||
//
|
||||
// The caller must keep the original Browser alive while the promoted InteractiveBrowser is in use,
|
||||
// since the Browser still owns the Playwright process and browser instance.
|
||||
//
|
||||
// Returns ErrNotPromotable if the Document is not backed by a Playwright page,
|
||||
// or ErrAlreadyDetached if the Document was already promoted.
|
||||
func PromoteToInteractive(doc Document) (InteractiveBrowser, error) {
|
||||
d, ok := doc.(*document)
|
||||
if !ok {
|
||||
return nil, ErrNotPromotable
|
||||
}
|
||||
|
||||
if d.detached {
|
||||
return nil, ErrAlreadyDetached
|
||||
}
|
||||
|
||||
d.detached = true
|
||||
|
||||
return &interactiveBrowser{
|
||||
pw: d.pw,
|
||||
browser: d.browser,
|
||||
ctx: d.page.Context(),
|
||||
page: d.page,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DemoteToDocument transfers ownership of the underlying Playwright page from an
|
||||
// InteractiveBrowser back to a new Document. After demotion, the InteractiveBrowser's
|
||||
// Close method becomes a no-op (the page is now owned by the returned Document).
|
||||
//
|
||||
// Returns ErrNotDemotable if the InteractiveBrowser is not backed by a Playwright page,
|
||||
// or ErrAlreadyDetached if the InteractiveBrowser was already demoted.
|
||||
func DemoteToDocument(ib InteractiveBrowser) (Document, error) {
|
||||
b, ok := ib.(*interactiveBrowser)
|
||||
if !ok {
|
||||
return nil, ErrNotDemotable
|
||||
}
|
||||
|
||||
if b.detached {
|
||||
return nil, ErrAlreadyDetached
|
||||
}
|
||||
|
||||
b.detached = true
|
||||
|
||||
return newDocument(b.pw, b.browser, b.page)
|
||||
}
|
||||
Reference in New Issue
Block a user