Compare commits

..

108 Commits

Author SHA1 Message Date
spouliot a947494cbd Merge dev into master: churned account filter, powder catalog lookup fixes 2026-05-14 14:19:27 -04:00
spouliot 7e79a13cb1 Fix powder catalog lookup: exact match auto-fills, partials show picker modal
- CatalogLookup now returns all partial color name matches ranked by
  specificity (exact vendor+color first, same-vendor partial, cross-vendor)
  with isExact flag so JS can decide to auto-fill vs show modal
- Removed cross-vendor fallback that was silently overwriting manufacturer
  field with wrong brand when vendor-scoped search found nothing
- Picker modal now includes "Not listed — search online" option that
  triggers AI lookup as an escape hatch from the catalog results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 14:18:52 -04:00
spouliot 2ad6df1195 Hide churned trial accounts from company/health screens by default
- Companies list and Company Health now hide Expired/Canceled accounts
  whose subscription ended 14+ days ago; show/hide toggle via banner
- KPI cards on Company Health exclude churned tenants when hidden
- showChurned param threads through sort, pagination, search, and filter forms
- Powder catalog: fix missing UnitPrice on user-contributed entries;
  add back-sync to fill catalog gaps on existing matches; wire
  AiAugmentFromUrl and manual inventory Create into catalog contribute path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:59:12 -04:00
spouliot dc3cd75ea4 Merge dev into master — prod deploy 2026-05-14
- Real-time SMS consent status update on customer record
- Fix kiosk SMS consent routing loop and stuck tablet
- Fix notification bell, SMS consent kiosk flow, and button alignment
- Add staff-presented SMS consent flow on customer record
- Customer intake kiosk (SignalR → polling, inactivity reset, signature pad, anonymous logo endpoint)
- Invoice SMS notifications
- Kiosk help article and AI knowledge base updates
2026-05-14 08:17:24 -04:00
spouliot a73f14fa7f Real-time SMS consent status update on customer record
When kiosk consent is completed, the staff-facing customer Details page
now updates the SMS badge instantly via SignalR — no page refresh needed.
Added customerId to the NewInAppNotification SignalR payload so the
KioskConsent handler can match the current URL and swap the badge in place.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 23:40:47 -04:00
spouliot 0af31c39b3 Fix kiosk SMS consent routing loop and stuck tablet
- Route param renamed customerId→id so /Kiosk/SmsConsent/15307 binds correctly
  (default MVC route uses {id}; mismatched name caused GetByIdAsync(0)→404→loop)
- Cache entry cleared in GET (not just POST) so returning to Welcome after seeing
  the form never redirects again
- Added POST /Kiosk/CancelSmsConsent for staff to free the kiosk if they pushed
  consent accidentally — Customer Details shows a Cancel button after pushing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 23:25:37 -04:00
spouliot e1256503be Fix notification bell, SMS consent kiosk flow, and button alignment
Notification bell:
- Bell now polls /InAppNotifications/Recent every 60s as a SignalR fallback
- Bell dropdown refresh on open so count is always current when staff looks at it

SMS consent → kiosk flow:
- Staff clicks "Get SMS Consent" on Customer Details → AJAX POST to
  /Kiosk/PushSmsConsent stores customer in IMemoryCache (10 min TTL)
- Kiosk PollSession returns smsConsentPending + customerId so tablet navigates
  to /Kiosk/SmsConsent/{customerId} automatically
- Customer reads TCPA consent on tablet, taps I Agree or No Thanks
- On agree: NotifyBySms/SmsConsentedAt/SmsConsentMethod set; in-app notification
  fires; cache cleared; tablet returns to Welcome
- Removed Customers/SmsConsent (staff-browser version); moved view to Kiosk/

Button alignment:
- kiosk.css: added display:flex + align-items:center + justify-content:center to
  all kiosk body buttons so content is centred vertically in tall button outlines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 23:13:57 -04:00
spouliot b69ff6db3a Add staff-presented SMS consent flow on customer record
- New GET/POST Customers/SmsConsent/{id}: full-screen kiosk-layout page staff
  opens and hands to the customer to read TCPA consent language and tap I Agree
- On agreement: sets Customer.NotifyBySms, SmsConsentedAt (UTC), SmsConsentMethod
  = "InPerson", clears SmsOptedOutAt
- Redirects back if customer has already consented (no double-consent)
- Customer Details: "Get SMS Consent" badge link shown when NotifyBySms is false;
  SMS on badge shows consent date on hover when consented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:50:49 -04:00
spouliot 66231822af Update kiosk help article and AI knowledge base for output setting
- Help article: new "Kiosk Output Setting" section explaining Quote vs Job modes and
  the Company Settings → Kiosk tab; Overview updated; Reviewing Submissions now lists
  "View Quote" and "View Job" separately; notification label corrected (Remote vs Walk-in)
- AI knowledge base: CUSTOMER INTAKE KIOSK section updated — output setting documented,
  submission outcome reflects Quote/Job branch, notification labels corrected, workflow
  entries split into Quote-mode and Job-mode variants, troubleshooting updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:39:39 -04:00
spouliot d5ad9fa073 Add KioskIntakeOutput company setting and fix kiosk submission bugs
- New CompanyPreferences.KioskIntakeOutput setting ("Quote" default / "Job"): controls
  what the kiosk creates on submission; shown as a card-style radio toggle in
  Company Settings → Kiosk tab
- KioskSession.LinkedQuoteId added so quote-first sessions link back to the draft quote
- Migration AddKioskIntakeOutputSetting applies both schema changes
- ProcessSubmissionAsync branches on setting: creates Draft quote (quote-first) or
  Pending job (job-first); save order fixed (CompleteAsync before using DB-assigned Id as FK)
- Terms.cshtml pricing paragraph is now dynamic: "subject to formal quote" for Quote mode,
  "team member will reach out about pricing" for Job mode
- Customer Intakes list: "View Quote" button appears when LinkedQuoteId is set
- Notification label fixed: Remote sessions now say "Remote Intake", not "Walk-in Intake"
- Inactivity reset shortened to 45 s on intake steps
- Signature pad: hosted locally (no CDN), canvas resize deferred via requestAnimationFrame
- AI photo upload: client-side compression to ≤1200px + AbortController 120 s timeout
- Help article and AI knowledge base updated with kiosk feature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:35:37 -04:00
spouliot d134dd51e5 Add Customer Intake Kiosk help article and knowledge base entry
- New Help article at /Help/CustomerIntakeKiosk covering setup, in-person
  and remote intake flows, what happens on submission, reviewing intakes,
  and troubleshooting (signature pad, connection issues, seed data)
- Add kiosk entry to _HelpNav under Operations
- Update HelpKnowledgeBase: nav overview, full kiosk section, two new
  common workflow entries (walk-in kiosk and remote intake)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:02:34 -04:00
spouliot 1df7c13abd Sweep kiosk intake submission for FK/null bugs
- Fix Jobs.Id FK violation: save job first with CompleteAsync() to get
  its DB-assigned Id, THEN set session.LinkedJobId and save again.
  Previously job.Id was still 0 when written to the nullable FK column.
- Replace ?? 1 fallbacks on JobStatusId/JobPriorityId with explicit
  InvalidOperationException — hardcoded 1 may not exist in the company's
  lookup tables; now fails loud with a clear message instead of an FK error.
- Add ValidateSessionState check to Terms POST so expired/already-submitted
  sessions don't re-run ProcessSubmissionAsync and create duplicate jobs.
- Null-guard session.JobDescription before slicing for notification snippet.
- Tighten catch block: wrap the fallback CompleteAsync in its own try/catch
  so a secondary failure doesn't mask the original error in logs.
- Swap Job.Description / SpecialInstructions: Description now holds the
  actual job description text; SpecialInstructions records the intake source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:53:10 -04:00
spouliot 4a8778504f Fix FK violation on kiosk intake submission: set JobPriorityId
ProcessSubmissionAsync was creating a Job without JobPriorityId, leaving
it as 0 which violates the FK to JobPriorityLookups. Look up the NORMAL
priority the same way JobsController does everywhere else.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:36:53 -04:00
spouliot f1d7054b3e Fix AI quote reliability on mobile: compress photos + add fetch timeout
- Compress photos client-side before uploading (1200px max, JPEG 85%):
  full-res phone photos (5-15 MB) → ~150-250 KB, dramatically reducing
  upload time on slow mobile connections and Anthropic processing time
- Add 120s AbortController to both AiAnalyzeItem fetch calls so a stalled
  mobile connection produces a clear 'timed out' error instead of spinning forever
- After 30s show 'Still analyzing… this can take a minute on mobile' to
  reassure users the request is in progress
- Reset loading text on retry so the slow-connection hint doesn't persist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:22:32 -04:00
spouliot 46b950baf2 Kiosk intake: 45-second inactivity reset to Welcome screen
_KioskLayout inactivity timer now reads ViewBag.InactivityTimeoutMs
(defaults to 5 min). PopulateKioskViewBagFromSession sets it to 45 s
on every intake step so an abandoned form auto-returns to the waiting
screen. Welcome screen and Confirmation page are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:16:34 -04:00
spouliot 4e9c9d321a Fix kiosk signature pad: host locally, fix canvas resize timing
- Download signature_pad 4.1.7 to wwwroot/lib/signature-pad/ to eliminate
  CDN SRI hash failures and network dependencies on the tablet
- Wrap resizeCanvas in requestAnimationFrame so offsetWidth is non-zero
  when measured (browser layout pass must complete first)
- Add guard for SignaturePad not defined (shows user-visible error instead
  of silent JS crash)
- Add scrollIntoView on signature validation error for better tablet UX

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:14:49 -04:00
spouliot 0c8723ef84 Fix sw.js: exclude /hubs/ and PollSession from SW interception
SW fetch() wraps SSE responses in a buffered Response, preventing SignalR
streaming — handshakes time out after 15s as a result. Exclude /hubs/ and
/Kiosk/PollSession so the browser handles them directly without SW wrapping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:45:03 -04:00
spouliot 377bb1ce38 Replace kiosk SignalR with polling — Azure App Service blocks anonymous hub handshakes
SignalR WebSocket and SSE both receive immediate 'Handshake was canceled' from the
server-side hub context. The 15-second delay between negotiate and SSE connect
reveals the handshake timer has expired before the transport opens — caused by Azure
App Service's ingress proxy resetting anonymous long-lived connections.

Replacement: /Kiosk/PollSession (anonymous GET, no-cache) queried every 3 seconds.
Returns the most recent Active InPerson session created in the last 60 seconds.
The kiosk navigates when hasSession=true. Status dot: gray->green on first success,
yellow on network error, blue when navigating. Removed signalr.min.js from kiosk layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:37:28 -04:00
spouliot 2acf54e1a9 Fix kiosk SignalR: skip WebSocket transport, add hubs/Kiosk to subscription bypass
1. kiosk-welcome.js: force SSE|LongPolling transport on the kiosk hub.
   Azure App Service's ingress proxy cancels anonymous WebSocket handshakes
   before the SignalR protocol exchange completes. SSE and long polling
   work fine for the low-frequency StartIntake push this hub needs.

2. SubscriptionMiddleware: add /hubs/ and /Kiosk/ to SkipPaths so a
   subscription redirect can never fire on a hub or kiosk request and
   abort the connection mid-handshake.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:20:06 -04:00
spouliot 0b24c320cd Fix kiosk intake routing, view names, and SignalR diagnostics
Three bugs identified:
1. Routing: /Kiosk/Intake/{token}/{action} had no matching route — 4-segment
   URL fell through the default 3-segment {controller}/{action}/{id?} route.
   Added explicit kiosk_intake route in Program.cs.

2. View names: Contact/Job/Terms/Confirmation actions returned View(model)
   which resolved to Views/Kiosk/{Action}.cshtml — those files don't exist.
   Views live in Views/Kiosk/Intake/. Fixed all six return statements.

3. Diagnostics: conn dot now starts gray ("Connecting...") and turns green
   only when SignalR actually connects. Red + message if no company ID or
   connection fails. Makes it easy to confirm the hub connection is live.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:28:16 -04:00
spouliot 350f2d7658 Fix duplicate logo on kiosk Welcome screen; enlarge welcome logo
Layout rendered a small logo at top of every kiosk page. Welcome.cshtml
also rendered its own centered logo, resulting in two logos. Suppress
the layout logo on the Welcome screen via HideLayoutLogo ViewBag flag.
Bump kiosk-welcome-logo from 120x280 to 200x420 for better presence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:21:27 -04:00
spouliot 856d202b78 Fix kiosk SignalR group: set ViewBag.CompanyId so tablet joins correct hub group
Without this, data-company-id was empty and the JS connected to kiosk- (no ID),
so StartSession signals never reached the tablet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:18:39 -04:00
spouliot 8caaa84eac Hide Start Intake button when kiosk not activated; relabel remote link
- Start Intake button only shows when company has an active kiosk token
- Remote Link button renamed to "Send Intake Link" for clarity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:00:09 -04:00
spouliot e70f7ee9f1 Fix kiosk logo: add anonymous Logo endpoint proxying blob storage
CompanySettings/Logo requires tenant context and fails on anonymous
kiosk pages. Added Kiosk/Logo which resolves the company from the
KioskDevice cookie and proxies the blob directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:55:44 -04:00
spouliot 6a918c2afc Add invoice SMS notifications and customer intake kiosk
Invoice SMS:
- Send Invoice modal now prompts Email/SMS/Both based on customer contact data
- New /invoice/{token} customer-facing view page with full line items and pay button
- PublicViewToken (permanent) added to Invoice; separate from expiring PaymentLinkToken
- InvoiceSent SMS default template added; customizable via Notification Templates settings
- {{viewUrl}} placeholder documented in template editor

Customer Intake Kiosk:
- Tablet kiosk flow: Contact → Job → Terms/Signature → Confirmation
- Remote link mode for off-site customers (lighter form, no signature)
- KioskHub (AllowAnonymous SignalR) for staff-to-tablet push without login
- Staff activates tablet via cookie; sends remote link manually
- Submitted sessions create Customer + Job automatically; fires in-app notification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:25:27 -04:00
spouliot 27bfd4db4d Close all GL entry gaps across the accounting surface
- Stripe payments/refunds/chargebacks now post DR/CR entries (PaymentController)
- Vendor credit void now reverses the posted GL lines (VendorCreditsController)
- Gift certificate issue/redeem/void post GL to account 2500 GC Liability;
  FinancialReportService Trial Balance + Balance Sheet include GC liability and
  breakage income; P&L shows deferred revenue deduction and breakage income line
- Customer deposits now post DR Checking / CR 2300 on record, reverse on delete;
  invoice auto-apply uses DR 2300 / CR AR (not a second bank debit); draft
  invoice delete reverses deposit-apply GL before the AR reversal
- Deposit.DepositAccountId column added; account 2300 seeded via migration
- InvoicesController.ApplyCredit now posts DR Sales Discounts / CR AR,
  consistent with CreditMemosController.Apply
- IssueRefund (cash/card) posts DR AR / CR Bank and sets Refund.DepositAccountId;
  refund modal gains a bank account selector hidden for store-credit path
- CancelRefund (cash/card) reverses the IssueRefund GL entries
- LedgerService GetAccountLedgerAsync + ComputePriorBalanceAsync now include
  Refunds, CreditMemoApplications, VendorCreditApplications, GC Liability (2500),
  and Customer Deposits (2300) so account ledger view and RecalculateAllAsync
  produce correct balances
- Three EF migrations applied: SeedSalesDiscountsAccount, AccountingGapsPhase2,
  AccountingDepositsGL
- Unit tests updated for new IAccountBalanceService constructor params (200/200)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 12:42:46 -04:00
spouliot 787d1504ef Label onboarding status and path badges on company detail
The two floating badges in the Onboarding > Setup Progress card had no
context — "Not Started" and a path name appeared without explanation.
Added inline "Status:" and "Path:" labels so both badges are immediately
readable without needing a tooltip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:32:08 -04:00
spouliot 726bebdce9 Consolidate company admin screens: health badge on list, tabbed detail page
Companies/Index:
- Added Health badge column (Healthy / At Risk / Critical / Never Active)
  with the numeric score in a tooltip; computed from the same signals as
  CompanyHealth/Index using the new shared CompanyHealthHelper

Companies/Details:
- Converted flat card layout to five tabs: Overview, Users, Subscription,
  Onboarding, Health; URL hash is preserved so the active tab survives
  page refresh and back navigation
- Subscription tab shows plan/status/dates with an expiry countdown and a
  "Manage Subscription & Features" button to the full Manage page
- Onboarding tab shows wizard completion, milestone progress bar, and
  first-activity dates (previously only on the standalone page)
- Health tab shows score gauge, risk badge, and individual risk signals
  with a link through to the full CompanyHealth dashboard
- JS moved to wwwroot/js/companies-details.js (avoids inline-script failures)

Infrastructure:
- Extracted ComputeHealth / ToRiskLevel / ChurnRisk to CompanyHealthHelper.cs
  (same Controllers namespace); CompanyHealthController delegates to it
- CompanyCountSummary extended with Jobs30Counts, Jobs90Counts, LastLoginDates
  (3 extra GROUP BY queries scoped to the current page IDs, not all companies)
- CompanyListDto gains HealthScore, HealthRisk, LastLoginDate

Navigation:
- Removed "Onboarding Progress" hub card from People & Activity; the data
  is now surfaced directly on the Companies/Details Onboarding tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:22:14 -04:00
spouliot 786b78e502 Add Online Now shortcut to Platform Admin nav with live user count
Adds a direct link to UserActivity/Online immediately below the People
& Activity hub entry so the current active-user count is visible at a
glance without navigating into the hub first. The count badge is
rendered using the already-injected OnlineUserTracker.GetActiveCount()
call and is hidden when the count is zero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:49:37 -04:00
spouliot cb1b6dceb6 PR 5 follow-up: boolean radio buttons and seed missing settings
- Replace true/false text display with Yes/No radio button groups for
  boolean platform settings; toggling auto-submits the form so no Edit
  modal is needed for flags
- IsBool() helper detects *Enabled, *AppliesToTrials, *TrialsEnabled keys
- Hide Edit button for boolean settings (radio buttons are the control)
- Add AI group icon (bi-robot) and description to the group header switch
- Add Max key detection to InputType/InputHint for number inputs
- Migration AddMissingPlatformSettings seeds 6 previously missing rows
  (SmsEnabled, TrialsEnabled, GracePeriodDays, GracePeriodAppliesToTrials,
  MaxTenants, AiCatalogPriceCheckEnabled) using IF NOT EXISTS guards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:40:16 -04:00
spouliot fb31fa7eb3 PR 5: Platform Settings UI redesign
- Section headers now show a group-specific icon, colored icon tile, and a one-line description explaining what each group controls (General, Notifications, Subscriptions, Quotes, Data Retention)
- Each setting now displays UpdatedAt and UpdatedBy metadata below the current value so operators can see when and by whom a setting was last changed
- Edit modal now uses type-appropriate inputs: number (with min=0, step=1) for *Days keys, email for *Email keys, url for BaseUrl, text otherwise; each type shows a contextual hint
- Key name shown in monospace below the label on desktop for operator reference
- Added SuccessMessage TempData alert at the top of the page
- No backend or DB changes — view-only redesign

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:19:52 -04:00
spouliot 637be701ea PR 4: Production guardrails for Seed Data and Storage Migration
- SeedData/Index: added prominent danger banner when running in Production (environment include="Production") so operators are clearly warned before writing to the live database; page remains accessible since seeding is occasionally valid in prod for new company onboarding
- StorageMigration/Index: added warning banner in Production explaining the tool is not needed now that Azure Blob migration is complete
- PlatformAdminController: hide Storage Migration hub card in Production via ShowStorageMigration flag (same WEBSITE_SITE_NAME pattern as ShowRawLogFiles); Seed Data card remains visible so prod onboarding stays reachable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:08:14 -04:00
spouliot e9cd67f5d9 PR 3: Observability back-links and terminology cleanup
- Added an Observability back-link at the top of all 7 Observability-area pages (AuditLog, SystemLogs, SystemInfo, AiUsageReport, UsageQuota, BannedIps, Diagnostics/Index) consistent with the Maintenance back-link pattern from PR 2
- Renamed company-level nav label and view title from "Notification Log" to "Email & SMS Log" to distinguish it clearly from the platform-level "Platform Notifications", "Audit Log", and "System Logs" surfaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:55:58 -04:00
spouliot 433090effd PR 2: normalize DiagnosticsController auth; add Maintenance back-links
- DiagnosticsController: replaced raw [Authorize(Roles = "SuperAdmin,Administrator")] with [Authorize(Policy = SuperAdminOnly)] to match every other platform-admin controller; added PowderCoating.Shared.Constants using directive
- DataPurge, DataExport, StorageMigration, SeedData: added "← Maintenance" breadcrumb link at the top of each page so operators know they are in the guarded maintenance area and can navigate back to the hub

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:50:11 -04:00
spouliot 4ca90f561e Fix price override ignored for catalog items; fix time entry delete UI
- QuotePricingAssemblyService: catalog no-coat items now respect PowderCostOverride instead of always using DefaultPrice
- PricingCalculationService: removed duplicate catalog fast-path in CalculateQuoteTotalsAsync that bypassed CalculateQuoteItemPriceAsync (and thus ignored PowderCostOverride); all items now go through the single authoritative path
- Jobs/Details.cshtml: timeTracking.del() and timeTracking.save() now call costing.load() after success so the time entry row and costing breakdown stay in sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:38:49 -04:00
spouliot f95397204c Fix platform admin subtle badge styling 2026-05-12 10:16:20 -04:00
spouliot 31d305b66a Group platform admin tools into hub pages
- add grouped platform admin hub pages, view models, and shared card UI\n- simplify the super admin nav and dashboard quick links around the new hubs\n- fix the AiQuoteService EstimatedMinutes assignment so the infrastructure project builds cleanly
2026-05-12 09:03:18 -04:00
spouliot 42a8c089d5 Fix AI photo quote always returning shop minimum price
Two bugs caused AI estimates to collapse to the shop minimum floor:

1. Coating rate with no guard: when a shop hadn't calibrated their
   coating gun (rate = 0), the prompt injected '~0 sqft/hr' paired
   with 'MUST use shop-specific rates' — Claude returned near-zero
   estimatedMinutes, zeroing labor cost and triggering the floor.
   Fixed to mirror the existing blast-rate guard: rate=0 now sends
   a fallback instruction to use conservative industry-average times.

2. Per-item minutes divided by quantity: both the system prompt and
   user prompt explicitly tell Claude to return estimatedMinutes 'per
   single item', but CalculatePricingPreview() was dividing by qty
   anyway. For qty > 1 this halved (or more) the labor cost, again
   pushing toward the floor. Removed the incorrect divide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:10:05 -04:00
spouliot 2c353f2e7f Three small UX fixes: customer contact validation, pagination dropdown width
- Customer validation now accepts Mobile Phone as a valid contact method;
  previously only Email or Phone satisfied the requirement
- Create customer hint text updated to match the new validation rule
- Pagination page-size dropdown gets min-width: 5rem so the number has
  breathing room from the caret arrow across all paginated lists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 18:20:43 -04:00
spouliot c02a5584b4 Hide Converted quotes from default listing to reduce clutter
The Quotes index now excludes Converted status by default so the list
stays focused on active work. Converted quotes remain accessible via
the status filter dropdown. Selecting any explicit status filter
(including Converted) bypasses the exclusion as expected.

Also consolidated the two GetQuoteStatusLookupsAsync calls into one
at the top of the action since the cache makes it free.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 16:45:09 -04:00
spouliot 17da692dce Fix two production billing bugs: invoice missing oven cost, quote stuck in Draft after send
Bug 1 — Invoice total didn't match job total for direct jobs:
- Root cause: all three item-save paths in JobsController passed null for
  ovenCostId, so FinalPrice/ShopSuppliesAmount were stored without oven cost
  while the Details page recalculated live with OvenCostId and showed higher.
- Add OvenBatchCost stored field to Job entity (migration AddJobOvenBatchCost,
  default 0 for existing rows).
- Fix Create, Edit, and UpdateItems to pass job.OvenCostId and save OvenBatchCost.
- Fix InvoicesController.Create GET for direct jobs to use stored OvenBatchCost
  and ShopSuppliesAmount as separate labeled lines instead of recalculating
  shop supplies from scratch (which excluded the oven cost base).

Bug 2 — Quote status stayed Draft after "Send Quote via Email":
- ResendQuote advanced the approval token and sent the email but never
  updated the status. Added Draft → Sent advancement (same guard used by
  the SMS send path) so the status updates on successful email send.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 10:39:49 -04:00
spouliot 656f830898 Add permission descriptions and role defaults to CompanyUsers Create/Edit
- Added form-text blurbs under every permission checkbox on both pages
  so admins know exactly what each permission unlocks at a glance
- Replaced single Accountant default with a full roleDefaults map covering
  Viewer, Worker, Accountant, and Manager roles
- Create page applies defaults on load and on role change (fresh form)
- Edit page preserves saved permissions on load; only resets to defaults
  when the role is explicitly changed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:26:21 -04:00
spouliot dde66c807f Update help docs and AI knowledge base for Accountant role and new permissions
- Settings.cshtml: add Accountant to roles table with description; add
  Fine-Grained Permissions subsection with a full table of all 16 permissions
  including the new Can Manage Bills & AP and Can Manage Accounting entries
- HelpKnowledgeBase.cs: add Accountant to ROLE AWARENESS section at top;
  add Accountant to USER MANAGEMENT roles list with auto-checked permissions
  note; add Fine-grained permissions paragraph documenting CanManageBills,
  CanManageAccounting, and Accountant role defaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 19:44:09 -04:00
spouliot feff0fa73d Add Accountant role and CanManageBills/CanManageAccounting permissions
- AppConstants: add Accountant to CompanyRoles; add CanManageBills and
  CanManageAccounting to Policies
- ApplicationUser: add CanManageBills and CanManageAccounting bool fields
- UserManagementDtos: expose new fields in all three DTOs
- ClaimsPrincipalFactory: emit ManageBills and ManageAccounting claims
- Program.cs: add CanManageBills and CanManageAccounting policies;
  update CanManageInvoices, CanViewReports, CanManagePurchaseOrders,
  and CanManageVendors to auto-pass for Accountant role
- BillsController: replace CanManageInventory with CanManageBills on
  all write actions (correct policy — bills are not inventory)
- BankReconciliationsController: replace CanManageJobs with
  CanManageAccounting on write actions
- CompanyUsersController: add Accountant to validCompanyRoles (both
  Create/Edit), legacyRole switch, and all permission assignment blocks
- Create/Edit views: add Accountant option to role dropdown; add
  CanManageBills and CanManageAccounting checkboxes; JS auto-checks
  financial permissions when Accountant role is selected
- Migration AddAccountantRolePermissions: adds columns + backfills
  CanManageBills=1 and CanManageAccounting=1 for all CompanyAdmin users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 19:42:53 -04:00
spouliot 59beba2e15 Update help docs and AI knowledge base for 4 new AI bookkeeping features
- Reports.cshtml: added AI Payment Risk Prediction, Ask Your Financials,
  and Bank Rec Auto-Match subsections under AI-Powered Reports; updated
  on-this-page nav with sub-links for all three
- AccountsPayable.cshtml: added Recurring Bill Detection section explaining
  pattern cards, frequency types, confidence badges, next expected date,
  and the 2-occurrence minimum
- HelpKnowledgeBase.cs: added Recurring Bill Detection to BILLS section;
  added AI Payment Risk Prediction and Ask Your Financials to REPORTS
  available-reports list; added features 12–15 to AI FEATURES section
  (Recurring Detection, Payment Risk, Financial Query, Bank Rec Auto-Match)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 19:30:39 -04:00
spouliot 959e323f3a Add 4 AI bookkeeping features
Feature 7: Bank Rec Auto-Match — AiSuggestMatches endpoint scores uncleared
transactions vs statement ending balance; AI Auto-Match panel in Reconcile.cshtml
with confidence highlights and Apply All button.

Feature 8: Late Payment Prediction — PredictLatePayments endpoint scores open AR
customers by risk (high/medium/low) using historical avg-days-to-pay + late rate;
rendered as badge table in AR Aging view via ar-aging-ai.js.

Feature 9: Natural Language Financial Queries — FinancialQuery GET page + RunFinancialQuery
POST; 12-month context snapshot pre-loaded; answers grounded in real data with
supporting facts, follow-up suggestions, session history, and example chips.

Feature 10: Recurring Bill Detection — RunRecurringDetection scans 12 months of bills
for vendor payment patterns (monthly/quarterly/annual); card grid view in Bills/RecurringDetection.cshtml
with confidence badges, next-expected-date, and suggested actions.

Supporting: 4 new DTO groups in AccountingAiDtos.cs, 4 method signatures in
IAccountingAiService.cs, 4 implementations in AccountingAiService.cs, 4 new
AiFeatures constants, 2 new Landing page AI report cards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 19:22:49 -04:00
spouliot e2f9e9ae4f Button consistency sweep + mobile responsiveness patches
- Standardize modal dismiss/cancel buttons to btn-outline-secondary across 70+ views
- Remove btn-sm from page-level Create and Back buttons (Index + Detail pages)
- Fix Edit buttons on Details pages: btn-secondary -> btn-warning
- Fix form Cancel/Back links: btn-secondary -> btn-outline-secondary
- Add 10 CSS patches to site.css for mobile/tablet responsiveness:
  top-navbar overflow prevention, page-header flex-wrap at 575px,
  table action button min-height override, notification dropdown width cap,
  tablet content padding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 19:04:10 -04:00
spouliot 328b195127 Design consistency audit fixes: alerts, cards, dark mode, utilities
Alert sweep (113 alerts, 79 files):
  All persistent static banners now carry alert-permanent so the
  layout's 5-second auto-dismiss cannot swallow guidance, warnings,
  or validation errors. Transient dismissible toasts left untouched.

CSS fixes (site.css):
  .card.shadow-sm      — strips rogue border from ~40 drifted cards
  .card-header.bg-white — rebinds to var(--bs-body-bg) so card
                          headers follow dark/light theme correctly
  Typography utilities  — .text-2xs (.68rem), .text-xs (.73rem)
  Token color classes   — .text-ember, .text-ok, .text-bad,
                          .text-warn, .text-cool, .bg-paper-2
  Layout utilities      — .mw-xs/sm/md/lg replace inline max-width
  Comment              — documents text-ember vs text-primary intent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 18:05:29 -04:00
spouliot f6d457fe0e Condense Operations sidebar: remove 5 items, tighten padding
Layer 1 — relocate off nav:
  Shop Display + Shop Mobile → Jobs page header (split dropdown on Blank Work Order)
  Powder Insights → Inventory page header button

Layer 2 — remove orphan section headers:
  "Main Menu" (only had Dashboard under it)
  "Reports" (only had Reports link under it)

Layer 3 — CSS density:
  nav-link padding 0.75rem → 0.55rem vertical
  nav-section-title padding 1rem/0.5rem → 0.65rem/0.3rem

Operations mode: 27 elements → 22. Scrollbar eliminated on standard screens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:57:09 -04:00
spouliot c65445b94e Move Job Templates from sidebar nav to Jobs page header button
Templates button sits alongside Board and Blank Work Order in the
Jobs index action bar. Nav item and the now-redundant "& Templates"
section title are removed, trimming one more item from the sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:49:10 -04:00
spouliot ccb094e57a Fix nav mode strip: underline-tab style + stable scrollbar gutter
Replace button-style strip with proper underline tabs — active side gets
an ember bottom-border matching the sidebar's existing active-link
language. Add scrollbar-gutter: stable so the sidebar interior width
does not shift when the scrollbar appears/disappears between modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:45:55 -04:00
spouliot 0204430fa5 Add Operations/Finance mode switcher to sidebar nav
Two-tab strip between Dashboard and the Operations section lets users
toggle between the shop-management view and the accounting view.
FOUC-prevention: server-side controller detection stamps data-nav-mode
on <html> before first paint; CSS hides the inactive side instantly.
JS (nav-mode.js) auto-switches to Finance when navigating to an
accounting controller, restores saved preference everywhere else.
Vendors appears in both modes; all 39 nav items carry data-nav tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:40:14 -04:00
spouliot 4fd9c52aaf Phase G: Add Budgeting and Year-End Close
Budgeting:
- Budget + BudgetLine entities with Jan–Dec monthly columns per GL account
- BudgetsController: Index, Create, Edit, SetDefault, Copy, Delete
- Copy action rolls a budget forward to a new fiscal year
- Budget vs. Actual report (BudgetVsActual): compares monthly budget amounts to
  real P&L by calling GetProfitAndLossAsync once per month; variance shown as
  favorable/unfavorable; year + budget selectors in header
- Views: Budgets/Index, Create, Edit with inline annual totals via budget-edit.js
- Nav link + report card on Landing

Year-End Close:
- YearEndClose entity records each closed year + JE reference for audit trail
- AccountsController.YearEndClose GET (history + form) + CloseYear POST
- Close zeroes all Revenue and Expense/COGS account balances into Retained Earnings
  via IAccountBalanceService and posts a supporting JE dated Dec 31
- Idempotency: rejects attempt to close an already-closed year
- Pre-close checklist in view to guide the workflow
- Nav link under Finance

Migration AddBudgetsAndYearEndClose applied

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:01:56 -04:00
spouliot fde24b09c9 Phase F: Add Invoice Write-Off, Fixed Assets, Period Locking, and 1099 Tracking
- Invoice Write-Off: WriteOff POST action in InvoicesController posts bad-debt JE
  (DR bad debt expense / CR AR), reduces customer balance, marks invoice WrittenOff;
  write-off modal added to Invoice Details view with expense account selector
- Fixed Assets: FixedAsset + FixedAssetDepreciationEntry entities with straight-line
  depreciation; FixedAssetsController (Index/Create/Edit/Details/PostDepreciation/Delete);
  PostDepreciation auto-generates one JE per asset per period, skips already-posted,
  fully-depreciated, and disposed assets; full CRUD views + nav link
- Period Locking: Company.BookLockedThrough field; AccountingPeriodValidator static helper;
  lock check added to JE Post and Bill Create (blocks backdating into closed periods);
  SetPeriodLock action + date picker UI in Company Settings Accounting section
- 1099 Tracking: Is1099Vendor flag on Vendor entity + DTOs; checkbox in Create/Edit views;
  TaxReporting1099 report action + view lists payments by year, flags vendors >= $600;
  report card added to Reports Landing
- Migration AddFixedAssetsLockAnd1099 applied

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 12:19:32 -04:00
spouliot a255893ada Add Credit Memos standalone management module
CreditMemosController with Index, Details, Create, Apply, and Void actions.
All business logic (atomic apply transaction, RemainingBalance cap,
customer.CreditBalance adjustment, auto-Paid invoice when BalanceDue hits zero)
mirrors the invoice-centric IssueCreditMemo/ApplyCredit/VoidCreditMemo actions in
InvoicesController but redirects back to the credit memo rather than an invoice.

Views: Index (stats bar, status+search filter, table), Details (two-col layout
with application history table and Bootstrap Apply/Void confirm modals),
Create (customer dropdown, amount, reason, notes, optional expiry).

Apply modal populates amount automatically from min(remaining credit,
invoice balance due) via credit-memo.js data-attribute wiring (no inline scripts).

Nav: Credit Memos added to Billing & Payments section in _Layout.

Build: 0 errors. Unit tests: 200/200.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:48:35 -04:00
spouliot d94612cc9c Fix 4 post-review issues found in accounting module audit
- Drop orphan VendorCreditId1 column from VendorCreditApplications (was
  scaffolded by EF because WithMany() lacked inverse navigation name;
  fixed WithMany() → WithMany(vc => vc.Applications) in ApplicationDbContext)
- Wire EarlyPaymentDiscount fields through full data path: added
  EarlyPaymentDiscountPercent/Days to CreateInvoiceDto, hidden inputs to
  Invoice Create view, and JS to populate from customer AJAX response
- Add missing [HttpGet] attribute to TaxRatesController.Index
- Document GenerateNow architecture exception with XML rationale

Migration DropOrphanVendorCreditId1 applied. Build: 0 errors, 168 warnings.
Unit tests: 200/200 passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:32:44 -04:00
spouliot 14026818e2 Phase H: Add Cash Flow Statement (direct / cash-basis method)
- CashFlowStatementDto (Operating, Investing, Financing sections; BeginningCash/EndingCash)
- CashFlowLineDto for Investing/Financing line items
- GetCashFlowStatementAsync on IFinancialReportService + implementation in FinancialReportService
- GenerateCashFlowStatementPdfAsync on IPdfService + QuestPDF implementation in PdfService
- ReportsController.CashFlowStatement GET + CashFlowStatementPdf GET with inline/download mode
- CashFlowStatement.cshtml view with date filter, 3-section cards, summary sidebar, methodology note
- Reports Landing page: Cash Flow Statement card added to Accounting section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:14:47 -04:00
spouliot 42eff3357e Phase G: Add Recurring Transactions (BackgroundService + CRUD UI)
- RecurringTemplate entity with Frequency/IntervalCount/NextFireDate/EndDate/MaxOccurrences/TemplateData JSON
- RecurringFrequency + RecurringTemplateType enums
- RecurringTransactionService BackgroundService: hourly check, creates Draft bills or immediate expenses, advances NextFireDate, auto-deactivates on limits
- RecurringTemplatesController: Index/Create/Edit/ToggleActive/Delete/GenerateNow (on-demand fire)
- Three views + external JS for type-toggle and dynamic bill line items
- Finance sidebar nav: Recurring Transactions
- Migration: AddRecurringTemplates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:08:36 -04:00
spouliot d3a5d827f9 Phase F: Customer/Vendor Statements, Payment Terms Parser, Tax Rates
F1: GetCustomerStatementAsync/GetVendorStatementAsync on IFinancialReportService;
    StatementLineDto; CustomerStatementDto/VendorStatementDto; Statement action on
    CustomersController + VendorsController; Statement views + PDF download via
    StatementPdfHelper (QuestPDF); Statement button on Customer/Vendor Details pages.

F2: PaymentTermsParser static helper (CalculateDueDate, ParseEarlyPaymentDiscount);
    EarlyPaymentDiscountPercent/Days on Invoice entity; GetCustomerPaymentTerms AJAX
    endpoint on InvoicesController auto-populates Terms + due date on customer select;
    early payment discount notice on Invoice Create.

F3: TaxRate entity (Name/Rate/State/IsDefault/IsActive, tenant-filtered);
    IUnitOfWork.TaxRates + UnitOfWork + ApplicationDbContext; TaxRatesController
    (Index/Create/Edit/Delete/ToggleActive, CompanyAdminOnly); GetTaxRateForCustomer
    AJAX endpoint; Tax Rates in Settings gear menu.

Also fixes AddVendorCredits migration: VendorCreditApplications FKs changed from
CASCADE to NoAction to resolve SQL Server error 1785 (multiple cascade paths).
Migration: AddPaymentTermsAndTaxRates applied locally; 200/200 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:55:22 -04:00
spouliot 1229081436 Phase E: Add Bank Reconciliation
- IsCleared + ClearedDate added to Payment, BillPayment, Expense entities
- BankReconciliation entity (account, statement date, beginning/ending balance, status)
- BankReconciliationStatus enum (InProgress, Completed)
- Migration AddBankReconciliation: new BankReconciliations table + IsCleared/ClearedDate columns
- IUnitOfWork/UnitOfWork wired with BankReconciliations repo
- BankReconciliationsController: Index, Create, Reconcile, ToggleCleared (AJAX), Complete, Report
- Reconcile view: deposit/payment checkboxes with live running balance and difference via JS
- Complete is gated: only enabled when difference == $0.00
- Nav: Bank Reconciliation added to Finance section in _Layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 00:10:38 -04:00
spouliot cf9dcfb4c1 Phase D: Add Vendor Credits (AP cycle completion)
- VendorCredit, VendorCreditLineItem, VendorCreditApplication entities
- VendorCreditStatus enum (Open, PartiallyApplied, Applied, Voided)
- Migration AddVendorCredits: three new tables
- IUnitOfWork/UnitOfWork wired with all three repositories
- VendorCreditsController: Index (status tabs), Create, Details, Post, Apply, Void
- Post action: DR AP, CR each expense line (reverses original expense)
- Apply action: links credit to bill, updates Bill.AmountPaid and bill status
- Views: Index (summary cards + table), Create (dynamic line grid), Details (apply panel)
- Nav: Vendor Credits added to Finance section in _Layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 00:03:14 -04:00
spouliot a33687f7bd Phase C: Add Manual Journal Entries (double-entry GL)
- JournalEntry + JournalEntryLine entities with Draft/Posted/Reversed lifecycle
- JournalEntryStatus enum (Draft, Posted, Reversed)
- Migration AddJournalEntries: two new tables with self-referencing reversal FK
- IUnitOfWork/UnitOfWork wired with JournalEntries + JournalEntryLines repos
- ApplicationDbContext: DbSets, tenant query filters, reversal FK config
- LedgerService: JE lines added as 10th source in GetAccountLedgerAsync and ComputePriorBalanceAsync
- JournalEntriesController: Index (All/Draft/Posted tabs), Create, Details, Post, Reverse, Delete
- Views: Index, Create (dynamic balanced line grid with running debit/credit totals), Details
- journal-entry-create.js: dynamic line management with balance indicator
- Nav: Journal Entries added to Finance section in _Layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:56:03 -04:00
spouliot 0afb474c3e Add Phase B: Inventory COGS auto-posting to GL on JobUsage transactions
When powder is consumed via a job (JobsController) or scan (InventoryController.LogUsage),
debit the item's CogsAccountId and credit its InventoryAccountId for the cost of the
quantity consumed (using AverageCost if available, else UnitCost). No-op when either
GL account is not configured on the InventoryItem.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:39:23 -04:00
spouliot 7e1676cfd7 Add Phase A accounting features: AP Aging, Trial Balance, Cash vs Accrual
- AP Aging report (GetApAgingAsync, controller actions, view, PDF export)
  mirrors AR Aging — groups open bills by vendor, buckets by days past due date
- Trial Balance report (GetTrialBalanceAsync, view, PDF export)
  uses Account.CurrentBalance, groups by AccountType, validates debits == credits
- Cash vs Accrual accounting method setting on Company entity
  switchable at any time — report-time only, no GL re-posting on change
  P&L cash: revenue = payments received; expenses = bills/expenses paid in period
  Balance Sheet cash: omits AR and AP lines (no receivables/payables concept)
  AccountingMethod badge shown on P&L and Balance Sheet views
- Migration A (AddAccountingMethod) applied, default = Accrual for all existing companies
- AP Aging and Trial Balance added to Reports Landing page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:34:54 -04:00
spouliot 379b0de885 Refactor: centralize accounting helpers, status constants, and query deduplication
- AccountingDropdownHelper: wired into BillsController and ExpensesController,
  replacing 35-40 lines of duplicated DB queries per controller
- AppConstants.StatusCodes: added Job.* and Quote.* constants to replace all
  magic status strings across Jobs, Quotes, Appointments, OvenScheduler,
  AiQuickQuote, QuoteApproval, and AccountingDropdownHelper
- AccountingRules: extracted IsNormalDebitBalance into shared Infrastructure
  helper; removed duplicate private method from AccountBalanceService and
  LedgerService (~50 lines deleted)
- AccountDataExportController: extracted 9 Fetch*Async methods (superset of
  includes) so Add*Sheet and Build*Csv no longer duplicate DB queries; each
  entity is queried once regardless of whether XLSX or CSV format is requested
- BillsController.Create and ExpensesController.Create wrapped in
  ExecuteInTransactionAsync; blob uploads moved after commit to keep
  financial data atomic and prevent orphaned blobs from rolling back
- Number generators (Appointments, CreditMemo, OvenBatch) fixed from full-table
  GetAllAsync to prefix-filtered FindAsync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:42:39 -04:00
spouliot edd7389d7d Refactor: extract shared helpers, fix field drift, add assembly services
- IJobItemAssemblyService / IQuotePricingAssemblyService: centralize job item
  and quote pricing construction that was duplicated across create, rework copy,
  and quote-to-job conversion paths
- BlobFileHelper: single ValidateUpload/GetContentType/SanitizeFileName used by
  6 blob services (JobPhoto, QuotePhoto, ProfilePhoto, CompanyLogo, Equipment,
  Catalog) and BillsController + ExpensesController, removing 8 private copies
- PagedResult<T>.From(): static factory eliminates 6-line boilerplate in 11
  controllers (Appointments, Customers, Equipment, Inventory, Invoices, Jobs,
  Maintenance, CompanyUsers, PlatformUsers, Quotes, Vendors)
- AccountingDropdownHelper: single LoadAsync() call replaces duplicate
  vendor/account/job queries in BillsController and ExpensesController
- JobTemplateItem: add IsSalesItem + Sku fields with migration; propagate
  through JobTemplatesController snapshot copy and GetTemplatesJson projection,
  and JobsController template-application path
- Test assertions updated for standardized BlobFileHelper error messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:12:33 -04:00
spouliot 61866e1d1e Add carried-over jobs section to Daily Board and fix tip visibility
Non-terminal jobs scheduled for past dates now appear in a red 'Carried
Over' section at the top of today's board so they can't silently disappear.
Also added alert-permanent to the board tip so the layout doesn't auto-dismiss it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 15:34:39 -04:00
spouliot bc9de38da3 Fix Cash account subtype missing from debit-normal balance check
AccountSubType.Cash was not included in IsNormalDebitBalance in both
AccountBalanceService and LedgerService, causing Cash accounts to be
treated as credit-normal. Payments deposited to a Cash account were
debited in the wrong direction, producing a negative balance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:13:24 -04:00
spouliot 2694863d07 Fix health check URL to use production custom domain
Jenkins agent cannot resolve linuxpcl.azurewebsites.net due to DNS
restrictions on the build machine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:08:30 -04:00
spouliot 8646fa83c8 Fix PowerShell syntax error in zip creation step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:56:12 -04:00
spouliot 796d084ea6 Fix zip entry paths to use forward slashes for Linux compatibility
ZipFile.CreateFromDirectory on Windows produces backslash paths in zip
entries. Linux treats backslash as a literal filename character so
wwwroot\css\site.css is never found at wwwroot/css/site.css. Build the
zip manually with Replace backslash→forward slash on each entry name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:46:24 -04:00
spouliot 6d23c63912 Stop app before deploy to prevent rsync file lock failures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:11:04 -04:00
spouliot 3803d16731 Fix prod Jenkins pipeline: add tests, restart, and health check stage
- Add Test stage (unit tests only) before migrations so bad code fails fast
- Add az webapp restart after async deploy to ensure new code is loaded
- Add Health Check stage that polls app for up to 3 minutes post-deploy
- Restore xcopy of wwwroot (needed for linux-x64 publish on Windows)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 21:44:51 -04:00
spouliot 29fd7163dc Merge dev into master: billing email, SMS consent, incoming powder, invoice/job fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 21:13:54 -04:00
spouliot 9a52e7fae5 Ad-hoc quote email, accounting improvements, AI lookup fix, and misc service updates
- Quotes: ad-hoc email modal on Quote Details lets staff send to an address not on file;
  QuotesController passes overrideEmail through to NotificationService
- Quotes/Details view: SMS consent display, email/SMS send button state based on consent
- Accounting module: AccountingDisplayHelpers for consistent ledger formatting;
  AccountsController + Accounts views improvements; AccountingEnums additions
- Bills/Expenses: AI account categorization fixes in BillsController and ExpensesController
- InventoryAiLookupService: TDS cure fallback no longer fires on AiAugmentFromUrl path
  (LookupByUrlAsync already has it built in — was double-fetching)
- PdfService: quote/invoice PDF updates
- PricingCalculationService: minor pricing logic fix
- QuoteProfile: mapping updates for new quote fields
- ApplicationDbContextModelSnapshot: catches up to all 4 migrations in this branch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:48:00 -04:00
spouliot 0d980e651a Add pricing breakdown and powder pre-fill to Job Details; surface voided invoice history
- Job Details: collapsible internal pricing breakdown card mirrors quote details breakdown
  (items subtotal, shop supplies, discount, rush fee, tax, total)
- Job Details: voided invoice history section shows previous invoices instead of hiding them
- Complete Job modal: pre-fills powder usage from QR-scanned / manually logged entries so
  staff don't double-log; consumes pre-logged credit per InventoryItemId before deducting net delta
- JobProfile: map ShopSuppliesAmount, ShopSuppliesPercent, IsRushJob, DiscountType/Value/Reason
  so the pricing breakdown has the data it needs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:47:44 -04:00
spouliot 3278152d83 Fix invoice re-creation after void; add payment terms selector and shop supplies line
- Voided invoices no longer block creating a new invoice for the same job: voided invoice's
  JobId FK is cleared so the unique index slot is freed for the replacement
- Invoice Details view shows voided invoices as history rather than hiding them
- Payment terms: standardized SelectList (Due on Receipt, Net 15/30/45/60/90, 2% 10 Net 30,
  COD) with custom-term preservation; invoice-due-date.js auto-updates Due Date on term change
- Shop supplies on direct (no-quote) jobs: InvoicesController derives the shop supplies line
  from the company rate when the job has no source quote to read the pre-agreed amount from
- Job entity: ShopSuppliesAmount + ShopSuppliesPercent fields preserved through job lifecycle
- Migration: AddShopSuppliesAmountToJob

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:47:34 -04:00
spouliot fc35fd123c Add IsIncoming inventory flag and catalog-to-incoming powder flow in item wizard
- InventoryItem.IsIncoming: marks powder ordered but not yet received; enables QR code
  printing on work orders while the shipment is in transit
- InventoryController.CreateIncomingFromCatalog: POST endpoint creates a 0-balance inventory
  record from a PowderCatalogItem and returns it in wizard-compatible shape
- item-wizard.js: custom coat tab now searches the platform powder catalog as a fallback;
  catalog results show an 'Add as Incoming Order' option; createIncomingFromCatalog POSTs
  to server and selects the new item without a page refresh
- QuoteItemCoatDto: CatalogItemId + AddAsIncoming fields so the wizard can signal server-side
  incoming-item creation during quote save
- Inventory Create/Edit/Index views: IsIncoming badge and field
- IInventoryAiLookupService: minor interface update
- Migration: AddInventoryIsIncoming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:47:19 -04:00
spouliot f40d58ac2e Add TCPA-compliant SMS consent tracking for prospect quotes
- Quote entity: ProspectSmsConsent (bool) + ProspectSmsConsentedAt (DateTime?) fields
- QuoteDtos: consent fields on Create/Update/Convert DTOs with TCPA guidance text
- Quote Create/Edit views: SMS consent checkbox shown when mobile number is entered
- Quote ConvertToCustomer view: staff must re-confirm consent carries over to customer record
- QuoteApproval: consent state exposed in ViewModel and ApprovalPage for transparency
- Consent timestamp cleared when prospect quote is linked to an existing customer
- Migration: AddProspectSmsConsent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:47:04 -04:00
spouliot fb979bc88d Add BillingEmail field for commercial customers; support comma-separated multi-email
- Customer entity + DTO: new BillingEmail field (accounting/invoicing address)
- Email fields now accept comma-separated lists; DTO validates each address individually
- NotificationService: SendToEmailListAsync helper fans out to all addresses in a list;
  NotifyQuoteSentAsync accepts optional overrideEmail so staff can send to an ad-hoc address
- Migration: AddCustomerBillingEmail
- Customer Create/Edit/Details views updated to show Billing Email field
- customer-billing-email.js: client-side helpers for billing email input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:46:53 -04:00
spouliot 12f784f34c Add Unit Price column to quote PDF
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:41:44 -04:00
spouliot 90f93b6e2f Fall back to ProspectEmail for CustomerEmail on prospect quotes
Prospect quotes have no CustomerId so Customer is null — email is stored
in ProspectEmail directly on the quote. The send-button visibility check
was always seeing null and showing the 'no contact info' warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:31:12 -04:00
spouliot 135fd6f8d7 Clarify no-contact warning to say 'mobile number' not 'phone'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:06:24 -04:00
spouliot ff231d9dd2 Set quote status to Converted and show job number link on quote details
- CreateJobFromQuote now sets QuoteStatusId to CONVERTED after creating the job
- Added ConvertedToJobNumber to QuoteDto, populated in Details action
- 'View Job' button on Quote Details now shows the job number (e.g. 'View Job JOB-2505-0001')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 20:47:45 -04:00
spouliot e3c76ce7ce Fix missing customer contact fields on QuoteDto mapping
CustomerEmail, CustomerMobilePhone, CustomerNotifyBySms, and
CustomerNotifyByEmail were added to QuoteDto but never mapped in
QuoteProfile, causing the email/SMS visibility logic on Quote Details
to always see null and show the 'no contact info' warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 20:14:38 -04:00
spouliot 2cfe093780 Share Mark Complete modal as partial view; hide install button after PWA install
- Extract _CompleteJobModal.cshtml partial; Details.cshtml uses PartialAsync
- Job board COMPLETED drop fetches partial via AJAX and shows modal in-place
- Add GET Jobs/CompleteJobModal action to load job data for the board modal
- install-app.js: persist installed state in localStorage; clears automatically when browser re-fires beforeinstallprompt after uninstall

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:55:37 -04:00
spouliot bbedaedeaa Redirect board COMPLETED drop to Details page for full completion flow
Dragging a card to the Completed column on the job board previously called
MoveCard directly, skipping email/SMS notifications, CompletedDate, powder
deduction, and the completion modal entirely.

Now detects the COMPLETED status code on the target column, reverts the
visual drag, and navigates to the job Details page with #completeModal in
the hash. Details.cshtml auto-opens the Mark Complete modal on arrival,
so the user goes through the same code path as the Complete Job button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:54:13 -04:00
spouliot acbd9f60be Hide email controls when no email on file; show SMS hint for quote/job events
- Quotes Create/Edit: hide 'Send via email' checkbox when customer has no
  email; show badge 'send via SMS from details' or 'SMS consent required'
  when customer has a mobile number. JS responds to customer dropdown change.
- Quotes Details: hide 'Send Quote via Email' button and approval email
  checkbox; hide SMS button when no mobile; show consent-required note.
- Jobs Details (Mark Complete modal): hide email checkbox; show
  'SMS notification will be sent' badge or consent-required note.
- Jobs Index (status modal): hide email row when customer has no email.
- Jobs Edit: hide 'Notify customer if status changes' when no email.
- Invoices Details: hide Send/Re-send buttons when no email (vs. disabled).

DTOs: added CustomerEmail + CustomerNotifyByEmail to JobDto/JobListDto;
added CustomerNotifyByEmail/CustomerMobilePhone/CustomerNotifyBySms to
QuoteDto. Mapped in JobProfile and QuotesController customer blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:32:08 -04:00
spouliot d3863c713b Add QuoteApprovedByCustomer notification type; fix wrong type logged on approval
QuoteDeclinedByCustomer was used for both approve and decline responses,
so approval notifications showed the wrong type in the log. Added a distinct
QuoteApprovedByCustomer = 16 enum value, wired up the correct type in
NotificationService, added default templates in both the service fallback
dictionary and SeedData, and updated placeholder hints in CompanySettings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:13:34 -04:00
spouliot 4085ff7c73 Advance quote to Sent status when approval link sent via SMS
The SMS path was sending the message but never updating QuoteStatusId or
SentDate, leaving the quote in Draft. Now mirrors the email send path:
transitions Draft → Sent and stamps SentDate on first send only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:04:25 -04:00
spouliot 96ae3639ae Fix duplicate name on quote PDF for non-commercial customers
Non-commercial individuals have their full name stored in both CompanyName
and ContactFirstName/ContactLastName, causing the PDF to render the name
twice. Skip the company name line when it matches the assembled contact name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:01:37 -04:00
spouliot 8c30d5bf5a Restore IncludePrepCost pricing for catalog items with abnormal prep
The flag should default off (catalog DefaultPrice used as-is) but remain
functional so users can opt in when a job needs exceptional prep time
(e.g. 120-min outgassing vs. the typical 30 min baked into the catalog price).

Previous commit removed this entirely — restoring with correct default behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:47:48 -04:00
spouliot 9292f3169c Fix catalog item pricing and rogue margin-points text
Catalog DefaultPrice is always the base price — removed the IncludePrepCost
gate that was adding prep service labor on top of catalog items. PrepServices
on catalog items exist for scheduling purposes only, not pricing.

Also fixed Razor syntax bug in Details.cshtml where @(expr).ToString("F1")
rendered the raw decimal followed by the literal string ".ToString("F1")"
instead of the formatted value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:43:42 -04:00
spouliot 810d5a5dc1 Update idempotent EF migration script for prod deploy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:23:22 -04:00
spouliot 8a09a91234 Merge dev into master 2026-05-06 16:20:49 -04:00
spouliot d8622b3187 Fix catalog item repricing on oven-only quote edits
QuoteItem was missing IncludePrepCost, so the Edit GET always deserialized
it as true (DTO default). On save, prep service labor was added on top of
the catalog base price, silently bumping prices whenever any quote field
(e.g. oven cycle minutes) was changed without touching items.

Migration defaults new column to false for catalog items and true for
non-catalog items (matching the wizard's historical defaults).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:04:45 -04:00
spouliot 2d8827ad5c Fix 'Oven (1 batch × 0 min)' display when OvenCycleMinutes is null
Quote.OvenCycleMinutes is nullable — null means 'use company default'.
The Details and DownloadPdf actions were converting null → 0 before passing
it to the view, so the pricing summary showed '× 0 min' even though the
oven cost was correctly calculated from DefaultOvenCycleMinutes.

Fix: resolve null against operatingCosts.DefaultOvenCycleMinutes in both
controller actions (DownloadPdf now loads operating costs for this). Added
a defensive > 0 guard in the view so the minutes clause is omitted entirely
if it still comes through as 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:35:38 -04:00
spouliot 4d10175ce3 Add oven batch cost to AI Quick Quote (1 batch, DefaultOvenCycleMinutes or 50 min)
Previously the quick quote omitted the oven charge entirely, so saved quotes
were under-priced relative to full quotes from the same items.

Pricing: CalculatePricing now calculates ovenBatchCost = (cycleMin/60) × OvenOperatingCostPerHour
using DefaultOvenCycleMinutes (fallback 50 min), then adds it to the total as a quote-level
charge matching how PricingCalculationService handles oven costs.

Save path: SaveQuickQuoteRequest gains OvenBatchCost + OvenCycleMinutes; the Quote record
now stores OvenBatchCost, OvenCycleMinutes, and Total = ItemsSubtotal + OvenBatchCost.

Display: results card shows a sub-line under the estimate price:
"incl. oven 1 batch 50 min: $12.00"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:20:10 -04:00
spouliot 9c4c20e8bd Fix false-positive 'source quote was modified' banner after job conversion
The banner fires when quote.UpdatedAt > job.QuoteSnapshotUpdatedAt. The
snapshot was captured before saving quote.ConvertedToJobId, so the EF
interceptor's automatic UpdatedAt stamp on that save always made the quote
appear newer than the snapshot — triggering the banner on every freshly
converted job even with no actual changes.

Fix: after saving ConvertedToJobId, re-stamp QuoteSnapshotUpdatedAt to the
quote's final UpdatedAt value and save the job once more. The snapshot now
includes the conversion write, so the comparison is equal (not "after") and
the banner stays hidden until the quote genuinely changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:46:39 -04:00
spouliot ecb285657a Fix sandblast-only toggle overflow and $0 AI quote pricing
Overflow: replaced Bootstrap form-check with an explicit flex row so the
two-line label (title + subtitle) never bleeds outside the card boundary.

$0 pricing: when sandblast-only was toggled on an AI item, manualUnitPrice
was cleared and isAiItem set to false. The pricing engine then returned $0
because no prep services with minutes were configured. Fix: preserve the AI
price when toggling sandblast-only, and keep isAiItem=true so the server
routes through the AI-price path (manualUnitPrice) rather than trying to
recalculate from prep labor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:29:03 -04:00
spouliot 0054b7d108 Fix NullReferenceException on Quote Details when quote total is zero
PricingBreakdown was only populated when quote.Total > 0, but the Details
view unconditionally dereferences PricingBreakdown.ItemsSubtotal. Sandblast-
only quotes can legitimately have a $0 total (no powder/oven costs), leaving
PricingBreakdown null and crashing the Details render.

Removed the Total > 0 guard from both Details action overloads — always
populate PricingBreakdown from the stored snapshot fields (all values are 0
for an unpriced or sandblast-only quote, which is safe for display).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:16:24 -04:00
spouliot 63a85b6ce9 Fix sandblast-only oven charge and wizard overflow; fix Jenkins test failures
Sandblast-only oven charge root cause:
renderCoatsList() is called on every step-3 render. When the sandblast-only
toggle was checked it cleared wz.data.coats to [], but renderCoatsList()
then saw an empty list and auto-called addCoatRow(), silently pushing a
Base Coat back into wz.data.coats. The server saw Coats.Any() = true and
included the item in the oven fraction calculation, producing an unexpected
oven batch charge. Fixed by bailing out of renderCoatsList() when
sandblastOnly is active, and added a matching safety net in
buildItemFromWizard() that forces coats:[] when sandblastOnly is true.

Also fixed sandblast-only toggle label overflow: subtitle span changed to
d-block so it wraps beneath the bold label instead of running inline.

Test fixes:
- DepositsController and GiftCertificatesController tests updated with the
  required ICompanyLogoService mock parameter added to the logo fix commit.
- Two PricingCalculationServiceTests updated to include a coat entry on
  each item, matching the service's updated requirement that only items
  with coating layers are considered for oven fraction calculation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:56:20 -04:00
spouliot 71caa93461 Fix unit test build failures after logo service and pricing changes
DepositsController and GiftCertificatesController gained a required
ICompanyLogoService constructor parameter in the PDF logo fix; their
test factories were not updated and failed to compile on Jenkins.
Added Mock.Of<ICompanyLogoService>() to both factory methods and the
missing using directive to DepositsControllerTests.

PricingCalculationService now only charges oven cost for items that
have explicit coating layers (Coats collection non-empty), because
sandblast/prep-only and labor items do not go in the oven. Two tests
that tested the old "all items count toward oven fraction" logic were
updated to include a single coat entry on each item, which restores
the expected oven fraction math without changing the tested behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:38:18 -04:00
spouliot 2e73cfab54 Miscellaneous UI and pricing updates from prior sessions
- PricingCalculationService: powder coverage and specific gravity math fixes
- Dashboard/Index: minor widget updates
- Jobs/Details, Jobs/Intake: shop floor and intake view improvements
- Quotes/Details: detail view updates
- GiftCertificates/Details: detail view update
- job-photos.js: photo gallery improvements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:27:37 -04:00
spouliot 74414c6c71 Add AI overload retry with model fallback and consolidate wizard errors
Anthropic returns overloaded_error (HTTP 529) during high-demand periods.
Previously this failed immediately with a generic error. Now the service
retries Sonnet once after 5s, then falls back to Haiku (a separate
capacity pool) after another 3s before giving up. If all three attempts
are overloaded the user sees a clear "high demand" message rather than a
generic error. Non-overload errors still log at Error level.

Also consolidated AI wizard error display in item-wizard.js: photo upload
failures were using browser alert() while analyze failures used the inline
red alert bar. All errors now go through aiShowError() so they always
appear consistently as the red bar below the Analyze button. Removed the
alert() fallback from aiShowError() itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:27:27 -04:00
spouliot a8fb56e8ec Fix company logo missing from PDFs and add AI photo save logging
When a tenant uploads a logo it is stored in Azure Blob Storage and
LogoData (the legacy DB byte[]) is cleared. All PDF controllers were
still reading the now-null LogoData, so logos never appeared on any
PDF after upload. Fixed by injecting ICompanyLogoService into all six
affected controllers (Quotes, Invoices, Deposits, GiftCertificates,
PurchaseOrders, CatalogItems) and loading the blob-stored logo first
before falling back to the legacy DB field.

Also added structured logging to the AI photo promotion path in
QuotesController Create/Edit POST so upload failures are visible in
production logs instead of silently swallowed.

Added onclick safety net to the Create and Edit quote submit buttons
so dynamically-injected hidden fields (AiPhotoTempIds) are written
before iOS Safari collects the form data on submit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:27:18 -04:00
spouliot ca4fb959aa Add Sales Tax Liability report with PDF and CSV export
Invoice-basis report showing taxable vs non-taxable sales, tax billed
by GL account, monthly trend table/chart, and full invoice detail grid.
Non-taxable invoice rows shaded grey for easy scanning. Quick-preset
date buttons (This Month, Last Month, YTD, Last Year) for common filing
periods. CSV export formatted for accountants and tax-filing software.
Gated behind AllowAccounting() like other financial reports.

- SalesTaxReportDto + 3 supporting DTOs in FinancialReportDtos.cs
- GetSalesTaxReportAsync on IFinancialReportService + implementation
- GenerateSalesTaxReportPdfAsync on IPdfService + QuestPDF implementation
- SalesTax / SalesTaxPdf / SalesTaxCsv actions in ReportsController
- Views/Reports/SalesTax.cshtml with Chart.js monthly trend chart
- Landing page card added to Finance section
- HelpKnowledgeBase and Help/Reports.cshtml updated with full docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:27:08 -04:00
spouliot b0d8d5c612 Merge dev: powder usage logging, time entry fix, Data Protection keys
- Fix time entry 500: parseInt destroyed GUID user IDs
- Inventory ledger: show edit pencil for Adjustment rows (scan-without-job)
- Inventory ledger: scan-based logs now appear in Powder Usage By Job tab
- Store Data Protection keys in SQL Server (non-production); migration AddDataProtectionKeys
- Fix mojibake characters across multiple views
- Fix subscription grace period tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 22:35:58 -04:00
1596 changed files with 613279 additions and 3929 deletions
+3 -1
View File
@@ -172,7 +172,9 @@
"Bash(Select-Object -First 20)",
"PowerShell(node -e \"require\\('fs'\\).existsSync\\(require\\('path'\\).join\\(process.cwd\\(\\), 'node_modules', 'sharp'\\)\\) ? console.log\\('sharp ok'\\) : console.log\\('no sharp'\\)\")",
"WebFetch(domain:www.powdercoatinglogix.com)",
"PowerShell($bytes = [System.IO.File]::ReadAllBytes\\('src/PowderCoating.Web/Views/Jobs/Details.cshtml'\\); $text = [System.Text.Encoding]::UTF8.GetString\\($bytes\\); $idx = $text.IndexOf\\('hasPowderData'\\); $snippet = $text.Substring\\($idx - 20, 250\\); [System.Text.Encoding]::Unicode.GetBytes\\($snippet\\) | Format-Hex | Select-Object -First 30)"
"PowerShell($bytes = [System.IO.File]::ReadAllBytes\\('src/PowderCoating.Web/Views/Jobs/Details.cshtml'\\); $text = [System.Text.Encoding]::UTF8.GetString\\($bytes\\); $idx = $text.IndexOf\\('hasPowderData'\\); $snippet = $text.Substring\\($idx - 20, 250\\); [System.Text.Encoding]::Unicode.GetBytes\\($snippet\\) | Format-Hex | Select-Object -First 30)",
"PowerShell($dll = \"C:\\\\Users\\\\spoul\\\\.nuget\\\\packages\\\\questpdf\\\\2024.12.3\\\\lib\\\\net6.0\\\\QuestPDF.dll\"; $asm = [Reflection.Assembly]::LoadFile\\($dll\\); $asm.GetTypes\\(\\) | Where-Object { $_.Name -eq \"ContainerExtensions\" } | ForEach-Object { $_.GetMethods\\(\\) | Where-Object { $_.Name -match \"Canvas|Rotat|Layer\" } | Select-Object Name } | Sort-Object Name -Unique)",
"PowerShell(Get-ChildItem \"C:\\\\Users\\\\spoul\\\\.nuget\\\\packages\\\\\" -ErrorAction SilentlyContinue | Where-Object { $_.Name -match \"quest|skia\" } | Select-Object Name)"
]
}
}
Vendored
+49 -1
View File
@@ -24,6 +24,17 @@ pipeline {
}
}
stage('Test') {
steps {
bat 'dotnet test tests\\PowderCoating.UnitTests --no-build -c Release --logger "trx;LogFileName=results.trx" --results-directory TestResults'
}
post {
always {
junit testResults: 'TestResults/*.trx', allowEmptyResults: true
}
}
}
stage('Run Migrations') {
steps {
bat 'dotnet tool install --global dotnet-ef 2>nul || dotnet tool update --global dotnet-ef 2>nul'
@@ -42,7 +53,18 @@ pipeline {
stage('Deploy to Azure') {
steps {
bat 'powershell -Command "Add-Type -Assembly System.IO.Compression.FileSystem; if (Test-Path deploy.zip) { Remove-Item deploy.zip }; [System.IO.Compression.ZipFile]::CreateFromDirectory(\'publish\', \'deploy.zip\')"'
powershell '''
Add-Type -Assembly System.IO.Compression.FileSystem
if (Test-Path deploy.zip) { Remove-Item deploy.zip }
$publishDir = (Resolve-Path "publish").Path
$zip = [System.IO.Compression.ZipFile]::Open("deploy.zip", "Create")
Get-ChildItem -Path $publishDir -Recurse -File | ForEach-Object {
$entryName = $_.FullName.Substring($publishDir.Length + 1).Replace("\\", "/")
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, $entryName, "Optimal") | Out-Null
}
$zip.Dispose()
Write-Host "deploy.zip created with forward-slash entry paths"
'''
withCredentials([azureServicePrincipal(
credentialsId: 'azure-pcl',
subscriptionIdVariable: 'AZ_SUB_ID',
@@ -57,6 +79,32 @@ pipeline {
}
}
}
stage('Health Check') {
steps {
powershell '''
$url = "https://app.powdercoatinglogix.com/"
$timeout = 180
$elapsed = 0
Write-Host "Polling $url for up to $timeout seconds..."
do {
Start-Sleep -Seconds 10
$elapsed += 10
try {
$r = Invoke-WebRequest $url -UseBasicParsing -TimeoutSec 10
if ($r.StatusCode -lt 400) {
Write-Host "App responded HTTP $($r.StatusCode) after ${elapsed}s"
exit 0
}
} catch {
Write-Host "[${elapsed}s] Not yet responding: $_"
}
} while ($elapsed -lt $timeout)
Write-Error "App did not come healthy within $timeout seconds"
exit 1
'''
}
}
}
post {
+32 -5
View File
@@ -1,10 +1,14 @@
Shop Management App TO DO List
==============================
-Inventory Lookup not always finding price for Columbia Coatings
-Logging powder usage and choosing a job doesn't record properly in the activity section of the powder itself
-Need to allow deleting of powder usage entries, or at least editing in case of a goof up
-Still random weird characters on a bunch of pages. Intake button for example on the jobs screen shows: Intake ✓
-Add feature to prep for events where we can generate coupons or gift certificates in bulk
Duplication refactor memory
C:/Users/spoul/.codex/memories/powdercoatingapp-refactor-plan-2026-05-07.md.
Current memory
C:/Users/spoul/.codex/memories/powdercoatingapp-quote-sync-extracted-2026-05-07.md
-Google review request email after a job
-Check my ChatGPT chat about surface area for a few solid ideas for the system
@@ -187,6 +191,29 @@ AI Agent item where we upload a picture and it will calculate the approximate sq
-Lookup Modal not showing ALL matches. Maybe make scrollable
-Pickup cure information from TDS Sheet if not found by AI Search
-ON AI Photo Quote page, when the AI info comes back we should scroll the modal window down so it's visible. It's not clear that new info has been added to the modal for all customers
-Inventory Lookup not always finding price for Columbia Coatings
-Logging powder usage and choosing a job doesn't record properly in the activity section of the powder itself
-Need to allow deleting of powder usage entries, or at least editing in case of a goof up
-Still random weird characters on a bunch of pages. Intake button for example on the jobs screen shows: Intake ✓
5/7/2026
-When editing a job/quote item from catalog, pre-select the item chosen please
-Move buttons to right side of job details page
-When completing a job, pull in powder usage already entered
-Fix invoice due date to match terms selected
-Invoice Status should not show on PDF unless PAID
-If we start with a job, shop supplies is not being added to the items
-If you delete an invoice attached to a job, the create invoice button keeps trying to go back to it
-Customer approval page doesn't show all charges (Oven time missing?)
-Time Logging default user to logged in user
-Add Print Invoice button or allow viewing the PDF
-If an invoice is voided, I cant create a new one from a job. Show voided invoice as history, but allow creating a new one.
-If a completed job is changed after an invoice is created, we need to update the invoice. Also need to be able to modify an invoice to add a discount or similar after it's created
-Add multiple email address for commercial customers (Accounting for invoices and contact for quotes)
-Support entering multiple email addresses (comma seperated) in each field
-If no email on file, then prompt for address to send to.
-When choosing a powder NOT in stock, can we incorporate our inventory lookup function to find a powder, link it to the quote, add it to the inventory with a 0lb balance and still put it on the "powder to order" list?
-When choosing a prospect for a quote, we need way to consent and enable SMS for them
Ideas Removed
=======================
+32 -3
View File
@@ -1,8 +1,33 @@
Shop Management App TO DO List
==============================
-Inventory Lookup not always finding price for Columbia Coatings
-Logging powder usage and choosing a job doesn't record properly in the activity section of the powder itself
-Need to allow deleting of powder usage entries, or at least editing in case of a goof up
-When editing a job/quote item from catalog, pre-select the item chosen please
-Move buttons to right side of job details page
-When completing a job, pull in powder usage already entered
-Fix invoice due date to match terms selected
-Invoice Status should not show on PDF unless PAID
-If we start with a job, shop supplies is not being added to the items
-If you delete an invoice attached to a job, the create invoice button keeps trying to go back to it
-Customer approval page doesn't show all charges (Oven time missing?)
-Time Logging default user to logged in user
-Add Print Invoice button or allow viewing the PDF
-If an invoice is voided, I cant create a new one from a job. Show voided invoice as history, but allow creating a new one.
-If a completed job is changed after an invoice is created, we need to update the invoice. Also need to be able to modify an invoice to add a discount or similar after it's created
-Add multiple email address for commercial customers (Accounting for invoices and contact for quotes)
-Support entering multiple email addresses (comma seperated) in each field
-If no email on file, then prompt for address to send to.
-When choosing a powder NOT in stock, can we incorporate our inventory lookup function to find a powder, link it to the quote, add it to the inventory with a 0lb balance and still put it on the "powder to order" list?
-When choosing a prospect for a quote, we need way to consent and enable SMS for them
Duplication refactor memory
C:/Users/spoul/.codex/memories/powdercoatingapp-refactor-plan-2026-05-07.md.
Current memory
C:/Users/spoul/.codex/memories/powdercoatingapp-quote-sync-extracted-2026-05-07.md
-Google review request email after a job
-Check my ChatGPT chat about surface area for a few solid ideas for the system
@@ -185,6 +210,10 @@ AI Agent item where we upload a picture and it will calculate the approximate sq
-Lookup Modal not showing ALL matches. Maybe make scrollable
-Pickup cure information from TDS Sheet if not found by AI Search
-ON AI Photo Quote page, when the AI info comes back we should scroll the modal window down so it's visible. It's not clear that new info has been added to the modal for all customers
-Inventory Lookup not always finding price for Columbia Coatings
-Logging powder usage and choosing a job doesn't record properly in the activity section of the powder itself
-Need to allow deleting of powder usage entries, or at least editing in case of a goof up
-Still random weird characters on a bunch of pages. Intake button for example on the jobs screen shows: Intake ✓
Ideas Removed
=======================
+47
View File
@@ -0,0 +1,47 @@
I looked through the reporting code, and I do not see a dedicated sales tax report.
What exists today:
- The report menu has Financial Summary, AR Aging, Balance Sheet, and Sales & Income, but no sales-tax-specific report card in /Y:/PCC/
PowderCoatingApp/src/PowderCoating.Web/Views/Reports/Landing.cshtml:147 and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Web/Views/Reports/
Landing.cshtml:211.
- The reporting service only exposes four finance reports in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Application/Interfaces/
IFinancialReportService.cs:13 through /Y:/PCC/PowderCoatingApp/src/PowderCoating.Application/Interfaces/IFinancialReportService.cs:22.
- Sales & Income does surface tax, but only as a total and per-invoice column in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Infrastructure/
Services/FinancialReportService.cs:406, /Y:/PCC/PowderCoatingApp/src/PowderCoating.Infrastructure/Services/FinancialReportService.cs:419,
and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Web/Views/Reports/SalesAndIncome.cshtml:268.
- The good news is the underlying data is already there: invoices store TaxAmount and SalesTaxAccountId in /Y:/PCC/PowderCoatingApp/src/
PowderCoating.Core/Entities/Invoice.cs:23 and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Core/Entities/Invoice.cs:55, and the chart of
accounts seeds 2200 Sales Tax Payable in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Infrastructure/Services/SeedDataService.Accounts.cs:59.
Plan Id recommend:
1. Add a new Sales Tax report under Reports > Finance with date range filters, matching the existing report pattern in /Y:/PCC/PowderCoatingApp/
src/PowderCoating.Web/Controllers/ReportsController.cs:993.
2. Build a SalesTaxReportDto plus GetSalesTaxReportAsync(...) in the reporting interface/service.
3. Phase 1 report contents:
- Total taxable sales
- Total non-taxed sales
- Total sales tax billed
- Breakdown by sales-tax liability account
- Breakdown by month
- Invoice detail grid: invoice date, invoice #, customer, subtotal, tax %, tax amount, total, amount paid, balance due, status, tax account
4. Add PDF export, and probably CSV too, since this is the kind of report people hand to accountants.
5. Put a report card on the Reports landing page and gate it the same way as the other accounting reports.
Two important caveats before we build it:
- The biggest decision is whether this should be invoice-basis or cash-basis. The current model naturally supports invoice-basis first.
- Refunds and credit memos do not appear to store a separate tax adjustment value in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Core/Entities/
Refund.cs:1 and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Core/Entities/CreditMemo.cs:1, so phase 1 would be best framed as “sales tax billed
on invoices,” with net tax adjustments as a phase 2 enhancement.
My recommendation is:
- Build phase 1 as an invoice-basis Sales Tax Liability report
- Label zero-tax invoices as non-taxed sales
- Add cash-basis and refund/credit tax adjustments later if you need filing-grade accuracy for more complex cases
If you want, I can turn this plan into the actual report next.
+32
View File
@@ -0,0 +1,32 @@
I looked through the reporting code, and I do not see a dedicated sales tax report.
What exists today:
- The report menu has Financial Summary, AR Aging, Balance Sheet, and Sales & Income, but no sales-tax-specific report card in /Y:/PCC/
PowderCoatingApp/src/PowderCoating.Web/Views/Reports/Landing.cshtml:147 and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Web/Views/Reports/
Landing.cshtml:211.
- The reporting service only exposes four finance reports in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Application/Interfaces/
IFinancialReportService.cs:13 through /Y:/PCC/PowderCoatingApp/src/PowderCoating.Application/Interfaces/IFinancialReportService.cs:22.
- Sales & Income does surface tax, but only as a total and per-invoice column in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Infrastructure/
Services/FinancialReportService.cs:406, /Y:/PCC/PowderCoatingApp/src/PowderCoating.Infrastructure/Services/FinancialReportService.cs:419,
and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Web/Views/Reports/SalesAndIncome.cshtml:268.
- The good news is the underlying data is already there: invoices store TaxAmount and SalesTaxAccountId in /Y:/PCC/PowderCoatingApp/src/
PowderCoating.Core/Entities/Invoice.cs:23 and /Y:/PCC/PowderCoatingApp/src/PowderCoating.Core/Entities/Invoice.cs:55, and the chart of
accounts seeds 2200 Sales Tax Payable in /Y:/PCC/PowderCoatingApp/src/PowderCoating.Infrastructure/Services/SeedDataService.Accounts.cs:59.
Plan Id recommend:
1. Add a new Sales Tax report under Reports > Finance with date range filters, matching the existing report pattern in /Y:/PCC/PowderCoatingApp/
src/PowderCoating.Web/Controllers/ReportsController.cs:993.
2. Build a SalesTaxReportDto plus GetSalesTaxReportAsync(...) in the reporting interface/service.
3. Phase 1 report contents:
- Total taxable sales
- Total non-taxed sales
- Total sales tax billed
- Breakdown by sales-tax liability account
- Breakdown by month
- Invoice detail grid: invoice date, invoice #, customer, subtotal, tax %, tax amount, total, amount paid, balance due, status, tax account
4. Add PDF export, and probably CSV too, since this is the kind of report people hand to accountants.
5. Put a report card on the Reports landing page and gate it the same way as the other accounting reports.
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../playwright/cli.js" "$@"
else
exec node "$basedir/../playwright/cli.js" "$@"
fi
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../playwright-core/cli.js" "$@"
else
exec node "$basedir/../playwright-core/cli.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\playwright-core\cli.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../playwright-core/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../playwright-core/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../playwright-core/cli.js" $args
} else {
& "node$exe" "$basedir/../playwright-core/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\playwright\cli.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../playwright/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../playwright/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../playwright/cli.js" $args
} else {
& "node$exe" "$basedir/../playwright/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+38
View File
@@ -0,0 +1,38 @@
{
"name": "web-scraping",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/playwright": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.59.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Portions Copyright (c) Microsoft Corporation.
Portions Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+5
View File
@@ -0,0 +1,5 @@
Playwright
Copyright (c) Microsoft Corporation
This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer),
available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE).
+3
View File
@@ -0,0 +1,3 @@
# playwright-core
This package contains the no-browser flavor of [Playwright](http://github.com/microsoft/playwright).
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
{
"comment": "Do not edit this file, use utils/roll_browser.js",
"browsers": [
{
"name": "chromium",
"revision": "1217",
"installByDefault": true,
"browserVersion": "147.0.7727.15",
"title": "Chrome for Testing"
},
{
"name": "chromium-headless-shell",
"revision": "1217",
"installByDefault": true,
"browserVersion": "147.0.7727.15",
"title": "Chrome Headless Shell"
},
{
"name": "chromium-tip-of-tree",
"revision": "1417",
"installByDefault": false,
"browserVersion": "148.0.7755.0",
"title": "Chrome Canary for Testing"
},
{
"name": "chromium-tip-of-tree-headless-shell",
"revision": "1417",
"installByDefault": false,
"browserVersion": "148.0.7755.0",
"title": "Chrome Canary Headless Shell"
},
{
"name": "firefox",
"revision": "1511",
"installByDefault": true,
"browserVersion": "148.0.2",
"title": "Firefox"
},
{
"name": "firefox-beta",
"revision": "1505",
"installByDefault": false,
"browserVersion": "148.0b9",
"title": "Firefox Beta"
},
{
"name": "webkit",
"revision": "2272",
"installByDefault": true,
"revisionOverrides": {
"mac14": "2251",
"mac14-arm64": "2251",
"debian11-x64": "2105",
"debian11-arm64": "2105",
"ubuntu20.04-x64": "2092",
"ubuntu20.04-arm64": "2092"
},
"browserVersion": "26.4",
"title": "WebKit"
},
{
"name": "ffmpeg",
"revision": "1011",
"installByDefault": true,
"revisionOverrides": {
"mac12": "1010",
"mac12-arm64": "1010"
}
},
{
"name": "winldd",
"revision": "1007",
"installByDefault": false
},
{
"name": "android",
"revision": "1001",
"installByDefault": false
}
]
}
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env node
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { program } = require('./lib/cli/programWithTestStub');
program.parse(process.argv);
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './types/types';
+32
View File
@@ -0,0 +1,32 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const minimumMajorNodeVersion = 18;
const currentNodeVersion = process.versions.node;
const semver = currentNodeVersion.split('.');
const [major] = [+semver[0]];
if (major < minimumMajorNodeVersion) {
console.error(
'You are running Node.js ' +
currentNodeVersion +
'.\n' +
`Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` +
'Please update your version of Node.js.'
);
process.exit(1);
}
module.exports = require('./lib/inprocess');
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import playwright from './index.js';
export const chromium = playwright.chromium;
export const firefox = playwright.firefox;
export const webkit = playwright.webkit;
export const selectors = playwright.selectors;
export const devices = playwright.devices;
export const errors = playwright.errors;
export const request = playwright.request;
export const _electron = playwright._electron;
export const _android = playwright._android;
export default playwright;
@@ -0,0 +1,65 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var androidServerImpl_exports = {};
__export(androidServerImpl_exports, {
AndroidServerLauncherImpl: () => AndroidServerLauncherImpl
});
module.exports = __toCommonJS(androidServerImpl_exports);
var import_playwrightServer = require("./remote/playwrightServer");
var import_playwright = require("./server/playwright");
var import_crypto = require("./server/utils/crypto");
var import_utilsBundle = require("./utilsBundle");
var import_progress = require("./server/progress");
class AndroidServerLauncherImpl {
async launchServer(options = {}) {
const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
const controller = new import_progress.ProgressController();
let devices = await controller.run((progress) => playwright.android.devices(progress, {
host: options.adbHost,
port: options.adbPort,
omitDriverInstall: options.omitDriverInstall
}));
if (devices.length === 0)
throw new Error("No devices found");
if (options.deviceSerialNumber) {
devices = devices.filter((d) => d.serial === options.deviceSerialNumber);
if (devices.length === 0)
throw new Error(`No device with serial number '${options.deviceSerialNumber}' was found`);
}
if (devices.length > 1)
throw new Error(`More than one device found. Please specify deviceSerialNumber`);
const device = devices[0];
const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
const server = new import_playwrightServer.PlaywrightServer({ mode: "launchServer", path, maxConnections: 1, preLaunchedAndroidDevice: device });
const wsEndpoint = await server.listen(options.port, options.host);
const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => device.close();
browserServer.kill = () => device.close();
device.on("close", () => {
server.close();
browserServer.emit("close");
});
return browserServer;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AndroidServerLauncherImpl
});
@@ -0,0 +1,77 @@
"use strict";
if (process.env.PW_INSTRUMENT_MODULES) {
const Module = require("module");
const originalLoad = Module._load;
const root = { name: "<root>", selfMs: 0, totalMs: 0, childrenMs: 0, children: [] };
let current = root;
const stack = [];
Module._load = function(request, _parent, _isMain) {
const node = { name: request, selfMs: 0, totalMs: 0, childrenMs: 0, children: [] };
current.children.push(node);
stack.push(current);
current = node;
const start = performance.now();
let result;
try {
result = originalLoad.apply(this, arguments);
} catch (e) {
current = stack.pop();
current.children.pop();
throw e;
}
const duration = performance.now() - start;
node.totalMs = duration;
node.selfMs = Math.max(0, duration - node.childrenMs);
current = stack.pop();
current.childrenMs += duration;
return result;
};
process.on("exit", () => {
function printTree(node, prefix, isLast, lines2, depth) {
if (node.totalMs < 1 && depth > 0)
return;
const connector = depth === 0 ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
const time = `${node.totalMs.toFixed(1).padStart(8)}ms`;
const self = node.children.length ? ` (self: ${node.selfMs.toFixed(1)}ms)` : "";
lines2.push(`${time} ${prefix}${connector}${node.name}${self}`);
const childPrefix = prefix + (depth === 0 ? "" : isLast ? " " : "\u2502 ");
const sorted2 = node.children.slice().sort((a, b) => b.totalMs - a.totalMs);
for (let i = 0; i < sorted2.length; i++)
printTree(sorted2[i], childPrefix, i === sorted2.length - 1, lines2, depth + 1);
}
let totalModules = 0;
function count(n) {
totalModules++;
n.children.forEach(count);
}
root.children.forEach(count);
const lines = [];
const sorted = root.children.slice().sort((a, b) => b.totalMs - a.totalMs);
for (let i = 0; i < sorted.length; i++)
printTree(sorted[i], "", i === sorted.length - 1, lines, 0);
const totalMs = root.children.reduce((s, c) => s + c.totalMs, 0);
process.stderr.write(`
--- Module load tree: ${totalModules} modules, ${totalMs.toFixed(0)}ms total ---
` + lines.join("\n") + "\n");
const flat = /* @__PURE__ */ new Map();
function gather(n) {
const existing = flat.get(n.name);
if (existing) {
existing.selfMs += n.selfMs;
existing.totalMs += n.totalMs;
existing.count++;
} else {
flat.set(n.name, { selfMs: n.selfMs, totalMs: n.totalMs, count: 1 });
}
n.children.forEach(gather);
}
root.children.forEach(gather);
const top50 = [...flat.entries()].sort((a, b) => b[1].selfMs - a[1].selfMs).slice(0, 50);
const flatLines = top50.map(
([mod, { selfMs, totalMs: totalMs2, count: count2 }]) => `${selfMs.toFixed(1).padStart(8)}ms self ${totalMs2.toFixed(1).padStart(8)}ms total (x${String(count2).padStart(3)}) ${mod}`
);
process.stderr.write(`
--- Top 50 modules by self time ---
` + flatLines.join("\n") + "\n");
});
}
@@ -0,0 +1,120 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserServerImpl_exports = {};
__export(browserServerImpl_exports, {
BrowserServerLauncherImpl: () => BrowserServerLauncherImpl
});
module.exports = __toCommonJS(browserServerImpl_exports);
var import_playwrightServer = require("./remote/playwrightServer");
var import_helper = require("./server/helper");
var import_playwright = require("./server/playwright");
var import_crypto = require("./server/utils/crypto");
var import_debug = require("./server/utils/debug");
var import_stackTrace = require("./utils/isomorphic/stackTrace");
var import_time = require("./utils/isomorphic/time");
var import_utilsBundle = require("./utilsBundle");
var validatorPrimitives = __toESM(require("./protocol/validatorPrimitives"));
var import_progress = require("./server/progress");
class BrowserServerLauncherImpl {
constructor(browserName) {
this._browserName = browserName;
}
async launchServer(options = {}) {
const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
const metadata = { id: "", startTime: 0, endTime: 0, type: "Internal", method: "", params: {}, log: [], internal: true };
const validatorContext = {
tChannelImpl: (names, arg, path2) => {
throw new validatorPrimitives.ValidationError(`${path2}: channels are not expected in launchServer`);
},
binary: "buffer",
isUnderTest: import_debug.isUnderTest
};
let launchOptions = {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? envObjectToArray(options.env) : void 0,
timeout: options.timeout ?? import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT
};
let browser;
try {
const controller = new import_progress.ProgressController(metadata);
browser = await controller.run(async (progress) => {
if (options._userDataDir !== void 0) {
const validator = validatorPrimitives.scheme["BrowserTypeLaunchPersistentContextParams"];
launchOptions = validator({ ...launchOptions, userDataDir: options._userDataDir }, "", validatorContext);
const context = await playwright[this._browserName].launchPersistentContext(progress, options._userDataDir, launchOptions);
return context._browser;
} else {
const validator = validatorPrimitives.scheme["BrowserTypeLaunchParams"];
launchOptions = validator(launchOptions, "", validatorContext);
return await playwright[this._browserName].launch(progress, launchOptions, toProtocolLogger(options.logger));
}
});
} catch (e) {
const log = import_helper.helper.formatBrowserLogs(metadata.log);
(0, import_stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
throw e;
}
const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
const server = new import_playwrightServer.PlaywrightServer({ mode: options._sharedBrowser ? "launchServerShared" : "launchServer", path, maxConnections: Infinity, preLaunchedBrowser: browser });
const wsEndpoint = await server.listen(options.port, options.host);
const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.process = () => browser.options.browserProcess.process;
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => browser.options.browserProcess.close();
browserServer[Symbol.asyncDispose] = browserServer.close;
browserServer.kill = () => browser.options.browserProcess.kill();
browserServer._disconnectForTest = () => server.close();
browserServer._userDataDirForTest = browser._userDataDirForTest;
browser.options.browserProcess.onclose = (exitCode, signal) => {
server.close();
browserServer.emit("close", exitCode, signal);
};
return browserServer;
}
}
function toProtocolLogger(logger) {
return logger ? (direction, message) => {
if (logger.isEnabled("protocol", "verbose"))
logger.log("protocol", "verbose", (direction === "send" ? "SEND \u25BA " : "\u25C0 RECV ") + JSON.stringify(message), [], {});
} : void 0;
}
function envObjectToArray(env) {
const result = [];
for (const name in env) {
if (!Object.is(env[name], void 0))
result.push({ name, value: String(env[name]) });
}
return result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserServerLauncherImpl
});
@@ -0,0 +1,308 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserActions_exports = {};
__export(browserActions_exports, {
codegen: () => codegen,
open: () => open,
pdf: () => pdf,
screenshot: () => screenshot
});
module.exports = __toCommonJS(browserActions_exports);
var import_fs = __toESM(require("fs"));
var import_os = __toESM(require("os"));
var import_path = __toESM(require("path"));
var playwright = __toESM(require("../.."));
var import_utils = require("../utils");
var import_utilsBundle = require("../utilsBundle");
async function launchContext(options, extraOptions) {
validateOptions(options);
const browserType = lookupBrowserType(options);
const launchOptions = extraOptions;
if (options.channel)
launchOptions.channel = options.channel;
launchOptions.handleSIGINT = false;
const contextOptions = (
// Copy the device descriptor since we have to compare and modify the options.
options.device ? { ...playwright.devices[options.device] } : {}
);
if (!extraOptions.headless)
contextOptions.deviceScaleFactor = import_os.default.platform() === "darwin" ? 2 : 1;
if (browserType.name() === "webkit" && process.platform === "linux") {
delete contextOptions.hasTouch;
delete contextOptions.isMobile;
}
if (contextOptions.isMobile && browserType.name() === "firefox")
contextOptions.isMobile = void 0;
if (options.blockServiceWorkers)
contextOptions.serviceWorkers = "block";
if (options.proxyServer) {
launchOptions.proxy = {
server: options.proxyServer
};
if (options.proxyBypass)
launchOptions.proxy.bypass = options.proxyBypass;
}
if (options.viewportSize) {
try {
const [width, height] = options.viewportSize.split(",").map((n) => +n);
if (isNaN(width) || isNaN(height))
throw new Error("bad values");
contextOptions.viewport = { width, height };
} catch (e) {
throw new Error('Invalid viewport size format: use "width,height", for example --viewport-size="800,600"');
}
}
if (options.geolocation) {
try {
const [latitude, longitude] = options.geolocation.split(",").map((n) => parseFloat(n.trim()));
contextOptions.geolocation = {
latitude,
longitude
};
} catch (e) {
throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
}
contextOptions.permissions = ["geolocation"];
}
if (options.userAgent)
contextOptions.userAgent = options.userAgent;
if (options.lang)
contextOptions.locale = options.lang;
if (options.colorScheme)
contextOptions.colorScheme = options.colorScheme;
if (options.timezone)
contextOptions.timezoneId = options.timezone;
if (options.loadStorage)
contextOptions.storageState = options.loadStorage;
if (options.ignoreHttpsErrors)
contextOptions.ignoreHTTPSErrors = true;
if (options.saveHar) {
contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" };
if (options.saveHarGlob)
contextOptions.recordHar.urlFilter = options.saveHarGlob;
contextOptions.serviceWorkers = "block";
}
let browser;
let context;
if (options.userDataDir) {
context = await browserType.launchPersistentContext(options.userDataDir, { ...launchOptions, ...contextOptions });
browser = context.browser();
} else {
browser = await browserType.launch(launchOptions);
context = await browser.newContext(contextOptions);
}
let closingBrowser = false;
async function closeBrowser() {
if (closingBrowser)
return;
closingBrowser = true;
if (options.saveStorage)
await context.storageState({ path: options.saveStorage }).catch((e) => null);
if (options.saveHar)
await context.close();
await browser.close();
}
context.on("page", (page) => {
page.on("dialog", () => {
});
page.on("close", () => {
const hasPage = browser.contexts().some((context2) => context2.pages().length > 0);
if (hasPage)
return;
closeBrowser().catch(() => {
});
});
});
process.on("SIGINT", async () => {
await closeBrowser();
(0, import_utils.gracefullyProcessExitDoNotHang)(130);
});
const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
context.setDefaultTimeout(timeout);
context.setDefaultNavigationTimeout(timeout);
delete launchOptions.headless;
delete launchOptions.executablePath;
delete launchOptions.handleSIGINT;
delete contextOptions.deviceScaleFactor;
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions, closeBrowser };
}
async function openPage(context, url) {
let page = context.pages()[0];
if (!page)
page = await context.newPage();
if (url) {
if (import_fs.default.existsSync(url))
url = "file://" + import_path.default.resolve(url);
else if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:") && !url.startsWith("data:"))
url = "http://" + url;
await page.goto(url);
}
return page;
}
async function open(options, url) {
const { context } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH });
await context._exposeConsoleApi();
await openPage(context, url);
}
async function codegen(options, url) {
const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options;
const tracesDir = import_path.default.join(import_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
const { context, browser, launchOptions, contextOptions, closeBrowser } = await launchContext(options, {
headless: !!process.env.PWTEST_CLI_HEADLESS,
executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH,
tracesDir
});
const donePromise = new import_utils.ManualPromise();
maybeSetupTestHooks(browser, closeBrowser, donePromise);
import_utilsBundle.dotenv.config({ path: "playwright.env" });
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
mode: "recording",
testIdAttributeName,
outputFile: outputFile ? import_path.default.resolve(outputFile) : void 0,
handleSIGINT: false
});
await openPage(context, url);
donePromise.resolve();
}
async function maybeSetupTestHooks(browser, closeBrowser, donePromise) {
if (!process.env.PWTEST_CLI_IS_UNDER_TEST)
return;
const logs = [];
require("playwright-core/lib/utilsBundle").debug.log = (...args) => {
const line = require("util").format(...args) + "\n";
logs.push(line);
process.stderr.write(line);
};
browser.on("disconnected", () => {
const hasCrashLine = logs.some((line) => line.includes("process did exit:") && !line.includes("process did exit: exitCode=0, signal=null"));
if (hasCrashLine) {
process.stderr.write("Detected browser crash.\n");
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
});
const close = async () => {
await donePromise;
await closeBrowser();
};
if (process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT) {
setTimeout(close, +process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT);
return;
}
let stdin = "";
process.stdin.on("data", (data) => {
stdin += data.toString();
if (stdin.startsWith("exit")) {
process.stdin.destroy();
close();
}
});
}
async function waitForPage(page, captureOptions) {
if (captureOptions.waitForSelector) {
console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
await page.waitForSelector(captureOptions.waitForSelector);
}
if (captureOptions.waitForTimeout) {
console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
}
}
async function screenshot(options, captureOptions, url, path2) {
const { context } = await launchContext(options, { headless: true });
console.log("Navigating to " + url);
const page = await openPage(context, url);
await waitForPage(page, captureOptions);
console.log("Capturing screenshot into " + path2);
await page.screenshot({ path: path2, fullPage: !!captureOptions.fullPage });
await page.close();
}
async function pdf(options, captureOptions, url, path2) {
if (options.browser !== "chromium")
throw new Error("PDF creation is only working with Chromium");
const { context } = await launchContext({ ...options, browser: "chromium" }, { headless: true });
console.log("Navigating to " + url);
const page = await openPage(context, url);
await waitForPage(page, captureOptions);
console.log("Saving as pdf into " + path2);
await page.pdf({ path: path2, format: captureOptions.paperFormat });
await page.close();
}
function lookupBrowserType(options) {
let name = options.browser;
if (options.device) {
const device = playwright.devices[options.device];
name = device.defaultBrowserType;
}
let browserType;
switch (name) {
case "chromium":
browserType = playwright.chromium;
break;
case "webkit":
browserType = playwright.webkit;
break;
case "firefox":
browserType = playwright.firefox;
break;
case "cr":
browserType = playwright.chromium;
break;
case "wk":
browserType = playwright.webkit;
break;
case "ff":
browserType = playwright.firefox;
break;
}
if (browserType)
return browserType;
import_utilsBundle.program.help();
}
function validateOptions(options) {
if (options.device && !(options.device in playwright.devices)) {
const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
for (const name in playwright.devices)
lines.push(` "${name}"`);
throw new Error(lines.join("\n"));
}
if (options.colorScheme && !["light", "dark"].includes(options.colorScheme))
throw new Error('Invalid color scheme, should be one of "light", "dark"');
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
codegen,
open,
pdf,
screenshot
});
@@ -0,0 +1,98 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var driver_exports = {};
__export(driver_exports, {
launchBrowserServer: () => launchBrowserServer,
printApiJson: () => printApiJson,
runDriver: () => runDriver,
runServer: () => runServer
});
module.exports = __toCommonJS(driver_exports);
var import_fs = __toESM(require("fs"));
var playwright = __toESM(require("../.."));
var import_pipeTransport = require("../server/utils/pipeTransport");
var import_playwrightServer = require("../remote/playwrightServer");
var import_server = require("../server");
var import_processLauncher = require("../server/utils/processLauncher");
function printApiJson() {
console.log(JSON.stringify(require("../../api.json")));
}
function runDriver() {
const dispatcherConnection = new import_server.DispatcherConnection();
new import_server.RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => {
const playwright2 = (0, import_server.createPlaywright)({ sdkLanguage });
return new import_server.PlaywrightDispatcher(rootScope, playwright2);
});
const transport = new import_pipeTransport.PipeTransport(process.stdout, process.stdin);
transport.onmessage = (message) => dispatcherConnection.dispatch(JSON.parse(message));
const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === "javascript";
const replacer = !isJavaScriptLanguageBinding && String.prototype.toWellFormed ? (key, value) => {
if (typeof value === "string")
return value.toWellFormed();
return value;
} : void 0;
dispatcherConnection.onmessage = (message) => transport.send(JSON.stringify(message, replacer));
transport.onclose = () => {
dispatcherConnection.onmessage = () => {
};
(0, import_processLauncher.gracefullyProcessExitDoNotHang)(0);
};
process.on("SIGINT", () => {
});
}
async function runServer(options) {
const {
port,
host,
path = "/",
maxConnections = Infinity,
extension,
artifactsDir
} = options;
const server = new import_playwrightServer.PlaywrightServer({ mode: extension ? "extension" : "default", path, maxConnections, artifactsDir });
const wsEndpoint = await server.listen(port, host);
process.on("exit", () => server.close().catch(console.error));
console.log("Listening on " + wsEndpoint);
process.stdin.on("close", () => (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0));
}
async function launchBrowserServer(browserName, configFile) {
let options = {};
if (configFile)
options = JSON.parse(import_fs.default.readFileSync(configFile).toString());
const browserType = playwright[browserName];
const server = await browserType.launchServer(options);
console.log(server.wsEndpoint());
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
launchBrowserServer,
printApiJson,
runDriver,
runServer
});
@@ -0,0 +1,171 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var installActions_exports = {};
__export(installActions_exports, {
installBrowsers: () => installBrowsers,
installDeps: () => installDeps,
markDockerImage: () => markDockerImage,
registry: () => import_server.registry,
uninstallBrowsers: () => uninstallBrowsers
});
module.exports = __toCommonJS(installActions_exports);
var import_path = __toESM(require("path"));
var import_server = require("../server");
var import_utils = require("../utils");
var import_utils2 = require("../utils");
var import_ascii = require("../server/utils/ascii");
function printInstalledBrowsers(browsers) {
const browserPaths = /* @__PURE__ */ new Set();
for (const browser of browsers)
browserPaths.add(browser.browserPath);
console.log(` Browsers:`);
for (const browserPath of [...browserPaths].sort())
console.log(` ${browserPath}`);
console.log(` References:`);
const references = /* @__PURE__ */ new Set();
for (const browser of browsers)
references.add(browser.referenceDir);
for (const reference of [...references].sort())
console.log(` ${reference}`);
}
function printGroupedByPlaywrightVersion(browsers) {
const dirToVersion = /* @__PURE__ */ new Map();
for (const browser of browsers) {
if (dirToVersion.has(browser.referenceDir))
continue;
const packageJSON = require(import_path.default.join(browser.referenceDir, "package.json"));
const version = packageJSON.version;
dirToVersion.set(browser.referenceDir, version);
}
const groupedByPlaywrightMinorVersion = /* @__PURE__ */ new Map();
for (const browser of browsers) {
const version = dirToVersion.get(browser.referenceDir);
let entries = groupedByPlaywrightMinorVersion.get(version);
if (!entries) {
entries = [];
groupedByPlaywrightMinorVersion.set(version, entries);
}
entries.push(browser);
}
const sortedVersions = [...groupedByPlaywrightMinorVersion.keys()].sort((a, b) => {
const aComponents = a.split(".");
const bComponents = b.split(".");
const aMajor = parseInt(aComponents[0], 10);
const bMajor = parseInt(bComponents[0], 10);
if (aMajor !== bMajor)
return aMajor - bMajor;
const aMinor = parseInt(aComponents[1], 10);
const bMinor = parseInt(bComponents[1], 10);
if (aMinor !== bMinor)
return aMinor - bMinor;
return aComponents.slice(2).join(".").localeCompare(bComponents.slice(2).join("."));
});
for (const version of sortedVersions) {
console.log(`
Playwright version: ${version}`);
printInstalledBrowsers(groupedByPlaywrightMinorVersion.get(version));
}
}
async function markDockerImage(dockerImageNameTemplate) {
(0, import_utils2.assert)(dockerImageNameTemplate, "dockerImageNameTemplate is required");
await (0, import_server.writeDockerVersion)(dockerImageNameTemplate);
}
async function installBrowsers(args, options) {
if ((0, import_utils.isLikelyNpxGlobal)()) {
console.error((0, import_ascii.wrapInASCIIBox)([
`WARNING: It looks like you are running 'npx playwright install' without first`,
`installing your project's dependencies.`,
``,
`To avoid unexpected behavior, please install your dependencies first, and`,
`then run Playwright's install command:`,
``,
` npm install`,
` npx playwright install`,
``,
`If your project does not yet depend on Playwright, first install the`,
`applicable npm package (most commonly @playwright/test), and`,
`then run Playwright's install command to download the browsers:`,
``,
` npm install @playwright/test`,
` npx playwright install`,
``
].join("\n"), 1));
}
if (options.shell === false && options.onlyShell)
throw new Error(`Only one of --no-shell and --only-shell can be specified`);
const shell = options.shell === false ? "no" : options.onlyShell ? "only" : void 0;
const executables = import_server.registry.resolveBrowsers(args, { shell });
if (options.withDeps)
await import_server.registry.installDeps(executables, !!options.dryRun);
if (options.dryRun && options.list)
throw new Error(`Only one of --dry-run and --list can be specified`);
if (options.dryRun) {
for (const executable of executables) {
console.log(import_server.registry.calculateDownloadTitle(executable));
console.log(` Install location: ${executable.directory ?? "<system>"}`);
if (executable.downloadURLs?.length) {
const [url, ...fallbacks] = executable.downloadURLs;
console.log(` Download url: ${url}`);
for (let i = 0; i < fallbacks.length; ++i)
console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
}
console.log(``);
}
} else if (options.list) {
const browsers = await import_server.registry.listInstalledBrowsers();
printGroupedByPlaywrightVersion(browsers);
} else {
await import_server.registry.install(executables, { force: options.force });
await import_server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || "javascript").catch((e) => {
e.name = "Playwright Host validation warning";
console.error(e);
});
}
}
async function uninstallBrowsers(options) {
delete process.env.PLAYWRIGHT_SKIP_BROWSER_GC;
await import_server.registry.uninstall(!!options.all).then(({ numberOfBrowsersLeft }) => {
if (!options.all && numberOfBrowsersLeft > 0) {
console.log("Successfully uninstalled Playwright browsers for the current Playwright installation.");
console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.
To uninstall Playwright browsers for all installations, re-run with --all flag.`);
}
});
}
async function installDeps(args, options) {
await import_server.registry.installDeps(import_server.registry.resolveBrowsers(args, {}), !!options.dryRun);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
installBrowsers,
installDeps,
markDockerImage,
registry,
uninstallBrowsers
});
@@ -0,0 +1,225 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var program_exports = {};
__export(program_exports, {
program: () => import_utilsBundle2.program
});
module.exports = __toCommonJS(program_exports);
var import_bootstrap = require("../bootstrap");
var import_utils = require("../utils");
var import_traceCli = require("../tools/trace/traceCli");
var import_utilsBundle = require("../utilsBundle");
var import_utilsBundle2 = require("../utilsBundle");
const packageJSON = require("../../package.json");
import_utilsBundle.program.version("Version " + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
import_utilsBundle.program.command("mark-docker-image [dockerImageNameTemplate]", { hidden: true }).description("mark docker image").allowUnknownOption(true).action(async function(dockerImageNameTemplate) {
const { markDockerImage } = require("./installActions");
markDockerImage(dockerImageNameTemplate).catch(logErrorAndExit);
});
commandWithOpenOptions("open [url]", "open page in browser specified via -b, --browser", []).action(async function(url, options) {
const { open } = require("./browserActions");
open(options, url).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ open
$ open -b webkit https://example.com`);
commandWithOpenOptions(
"codegen [url]",
"open page and generate code for user actions",
[
["-o, --output <file name>", "saves the generated script to a file"],
["--target <language>", `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()],
["--test-id-attribute <attributeName>", "use the specified attribute to generate data test ID selectors"]
]
).action(async function(url, options) {
const { codegen } = require("./browserActions");
await codegen(options, url);
}).addHelpText("afterAll", `
Examples:
$ codegen
$ codegen --target=python
$ codegen -b webkit https://example.com`);
import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--list", "prints list of browsers from all playwright installations").option("--force", "force reinstall of already installed browsers").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) {
try {
const { installBrowsers } = require("./installActions");
await installBrowsers(args, options);
} catch (e) {
console.log(`Failed to install browsers
${e}`);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText("afterAll", `
Examples:
- $ install
Install default browsers.
- $ install chrome firefox
Install custom browsers, supports chromium, firefox, webkit, chromium-headless-shell.`);
import_utilsBundle.program.command("uninstall").description("Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.").option("--all", "Removes all browsers used by any Playwright installation from the system.").action(async (options) => {
const { uninstallBrowsers } = require("./installActions");
uninstallBrowsers(options).catch(logErrorAndExit);
});
import_utilsBundle.program.command("install-deps [browser...]").description("install dependencies necessary to run browsers (will ask for sudo permissions)").option("--dry-run", "Do not execute installation commands, only print them").action(async function(args, options) {
try {
const { installDeps } = require("./installActions");
await installDeps(args, options);
} catch (e) {
console.log(`Failed to install browser dependencies
${e}`);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText("afterAll", `
Examples:
- $ install-deps
Install dependencies for default browsers.
- $ install-deps chrome firefox
Install dependencies for specific browsers, supports chromium, firefox, webkit, chromium-headless-shell.`);
const browsers = [
{ alias: "cr", name: "Chromium", type: "chromium" },
{ alias: "ff", name: "Firefox", type: "firefox" },
{ alias: "wk", name: "WebKit", type: "webkit" }
];
for (const { alias, name, type } of browsers) {
commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(async function(url, options) {
const { open } = require("./browserActions");
open({ ...options, browser: type }, url).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ ${alias} https://example.com`);
}
commandWithOpenOptions(
"screenshot <url> <filename>",
"capture a page screenshot",
[
["--wait-for-selector <selector>", "wait for selector before taking a screenshot"],
["--wait-for-timeout <timeout>", "wait for timeout in milliseconds before taking a screenshot"],
["--full-page", "whether to take a full page screenshot (entire scrollable area)"]
]
).action(async function(url, filename, command) {
const { screenshot } = require("./browserActions");
screenshot(command, command, url, filename).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ screenshot -b webkit https://example.com example.png`);
commandWithOpenOptions(
"pdf <url> <filename>",
"save page as pdf",
[
["--paper-format <format>", "paper format: Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6"],
["--wait-for-selector <selector>", "wait for given selector before saving as pdf"],
["--wait-for-timeout <timeout>", "wait for given timeout in milliseconds before saving as pdf"]
]
).action(async function(url, filename, options) {
const { pdf } = require("./browserActions");
pdf(options, options, url, filename).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ pdf https://example.com example.pdf`);
import_utilsBundle.program.command("run-driver", { hidden: true }).action(async function(options) {
const { runDriver } = require("./driver");
runDriver();
});
import_utilsBundle.program.command("run-server", { hidden: true }).option("--port <port>", "Server port").option("--host <host>", "Server host").option("--path <path>", "Endpoint Path", "/").option("--max-clients <maxClients>", "Maximum clients").option("--mode <mode>", 'Server mode, either "default" or "extension"').option("--artifacts-dir <artifactsDir>", "Artifacts directory").action(async function(options) {
const { runServer } = require("./driver");
runServer({
port: options.port ? +options.port : void 0,
host: options.host,
path: options.path,
maxConnections: options.maxClients ? +options.maxClients : Infinity,
extension: options.mode === "extension" || !!process.env.PW_EXTENSION_MODE,
artifactsDir: options.artifactsDir
}).catch(logErrorAndExit);
});
import_utilsBundle.program.command("print-api-json", { hidden: true }).action(async function(options) {
const { printApiJson } = require("./driver");
printApiJson();
});
import_utilsBundle.program.command("launch-server", { hidden: true }).requiredOption("--browser <browserName>", 'Browser name, one of "chromium", "firefox" or "webkit"').option("--config <path-to-config-file>", "JSON file with launchServer options").action(async function(options) {
const { launchBrowserServer } = require("./driver");
launchBrowserServer(options.browser, options.config);
});
import_utilsBundle.program.command("show-trace [trace]").option("-b, --browser <browserType>", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("-h, --host <host>", "Host to serve trace on; specifying this option opens trace in a browser tab").option("-p, --port <port>", "Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab").option("--stdin", "Accept trace URLs over stdin to update the viewer").description("show trace viewer").action(async function(trace, options) {
if (options.browser === "cr")
options.browser = "chromium";
if (options.browser === "ff")
options.browser = "firefox";
if (options.browser === "wk")
options.browser = "webkit";
const openOptions = {
host: options.host,
port: +options.port,
isServer: !!options.stdin
};
const { runTraceInBrowser, runTraceViewerApp } = require("../server/trace/viewer/traceViewer");
if (options.port !== void 0 || options.host !== void 0)
runTraceInBrowser(trace, openOptions).catch(logErrorAndExit);
else
runTraceViewerApp(trace, options.browser, openOptions).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ show-trace
$ show-trace https://example.com/trace.zip`);
(0, import_traceCli.addTraceCommands)(import_utilsBundle.program, logErrorAndExit);
import_utilsBundle.program.command("cli", { hidden: true }).allowExcessArguments(true).allowUnknownOption(true).action(async (options) => {
const { program: cliProgram } = require("../tools/cli-client/program");
process.argv.splice(process.argv.indexOf("cli"), 1);
cliProgram().catch(logErrorAndExit);
});
function logErrorAndExit(e) {
if (process.env.PWDEBUGIMPL)
console.error(e);
else
console.error(e.name + ": " + e.message);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
function codegenId() {
return process.env.PW_LANG_NAME || "playwright-test";
}
function commandWithOpenOptions(command, description, options) {
let result = import_utilsBundle.program.command(command).description(description);
for (const option of options)
result = result.option(option[0], ...option.slice(1));
return result.option("-b, --browser <browserType>", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("--block-service-workers", "block service workers").option("--channel <channel>", 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option("--color-scheme <scheme>", 'emulate preferred color scheme, "light" or "dark"').option("--device <deviceName>", 'emulate device, for example "iPhone 11"').option("--geolocation <coordinates>", 'specify geolocation coordinates, for example "37.819722,-122.478611"').option("--ignore-https-errors", "ignore https errors").option("--load-storage <filename>", "load context storage state from the file, previously saved with --save-storage").option("--lang <language>", 'specify language / locale, for example "en-GB"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--save-har <filename>", "save HAR file with all network activity at the end").option("--save-har-glob <glob pattern>", "filter entries in the HAR by matching url against this glob pattern").option("--save-storage <filename>", "save context storage state at the end, for later use with --load-storage").option("--timezone <time zone>", 'time zone to emulate, for example "Europe/Rome"').option("--timeout <timeout>", "timeout for Playwright actions in milliseconds, no timeout by default").option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <directory>", "use the specified user data directory instead of a new context").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"');
}
function buildBasePlaywrightCLICommand(cliTargetLang) {
switch (cliTargetLang) {
case "python":
return `playwright`;
case "java":
return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`;
case "csharp":
return `pwsh bin/Debug/netX/playwright.ps1`;
default: {
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
return `${packageManagerCommand} playwright`;
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
program
});
@@ -0,0 +1,74 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var programWithTestStub_exports = {};
__export(programWithTestStub_exports, {
program: () => import_program2.program
});
module.exports = __toCommonJS(programWithTestStub_exports);
var import_processLauncher = require("../server/utils/processLauncher");
var import_utils = require("../utils");
var import_program = require("./program");
var import_program2 = require("./program");
function printPlaywrightTestError(command) {
const packages = [];
for (const pkg of ["playwright", "playwright-chromium", "playwright-firefox", "playwright-webkit"]) {
try {
require.resolve(pkg);
packages.push(pkg);
} catch (e) {
}
}
if (!packages.length)
packages.push("playwright");
const packageManager = (0, import_utils.getPackageManager)();
if (packageManager === "yarn") {
console.error(`Please install @playwright/test package before running "yarn playwright ${command}"`);
console.error(` yarn remove ${packages.join(" ")}`);
console.error(" yarn add -D @playwright/test");
} else if (packageManager === "pnpm") {
console.error(`Please install @playwright/test package before running "pnpm exec playwright ${command}"`);
console.error(` pnpm remove ${packages.join(" ")}`);
console.error(" pnpm add -D @playwright/test");
} else {
console.error(`Please install @playwright/test package before running "npx playwright ${command}"`);
console.error(` npm uninstall ${packages.join(" ")}`);
console.error(" npm install -D @playwright/test");
}
}
const kExternalPlaywrightTestCommands = [
["test", "Run tests with Playwright Test."],
["show-report", "Show Playwright Test HTML report."],
["merge-reports", "Merge Playwright Test Blob reports"]
];
function addExternalPlaywrightTestCommands() {
for (const [command, description] of kExternalPlaywrightTestCommands) {
const playwrightTest = import_program.program.command(command).allowUnknownOption(true).allowExcessArguments(true);
playwrightTest.description(`${description} Available in @playwright/test package.`);
playwrightTest.action(async () => {
printPlaywrightTestError(command);
(0, import_processLauncher.gracefullyProcessExitDoNotHang)(1);
});
}
}
if (!process.env.PW_LANG_NAME)
addExternalPlaywrightTestCommands();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
program
});
@@ -0,0 +1,361 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var android_exports = {};
__export(android_exports, {
Android: () => Android,
AndroidDevice: () => AndroidDevice,
AndroidInput: () => AndroidInput,
AndroidSocket: () => AndroidSocket,
AndroidWebView: () => AndroidWebView
});
module.exports = __toCommonJS(android_exports);
var import_eventEmitter = require("./eventEmitter");
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_waiter = require("./waiter");
var import_timeoutSettings = require("./timeoutSettings");
var import_rtti = require("../utils/isomorphic/rtti");
var import_time = require("../utils/isomorphic/time");
var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
var import_connect = require("./connect");
class Android extends import_channelOwner.ChannelOwner {
static from(android) {
return android._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async devices(options = {}) {
const { devices } = await this._channel.devices(options);
return devices.map((d) => AndroidDevice.from(d));
}
async launchServer(options = {}) {
if (!this._serverLauncher)
throw new Error("Launching server is not supported");
return await this._serverLauncher.launchServer(options);
}
async connect(endpoint, options = {}) {
return await this._wrapApiCall(async () => {
const deadline = options.timeout ? (0, import_time.monotonicTime)() + options.timeout : 0;
const headers = { "x-playwright-browser": "android", ...options.headers };
const connectParams = { endpoint, headers, slowMo: options.slowMo, timeout: options.timeout || 0 };
const connection = await (0, import_connect.connectToEndpoint)(this._connection, connectParams);
let device;
connection.on("close", () => {
device?._didClose();
});
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preConnectedAndroidDevice) {
connection.close();
throw new Error("Malformed endpoint. Did you use Android.launchServer method?");
}
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
device._shouldCloseConnectionOnClose = true;
device.on(import_events.Events.AndroidDevice.Close, () => connection.close());
return device;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
connection.close();
throw new Error(`Timeout ${options.timeout}ms exceeded`);
}
});
}
}
class AndroidDevice extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._webViews = /* @__PURE__ */ new Map();
this._shouldCloseConnectionOnClose = false;
this._android = parent;
this.input = new AndroidInput(this);
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, parent._timeoutSettings);
this._channel.on("webViewAdded", ({ webView }) => this._onWebViewAdded(webView));
this._channel.on("webViewRemoved", ({ socketName }) => this._onWebViewRemoved(socketName));
this._channel.on("close", () => this._didClose());
}
static from(androidDevice) {
return androidDevice._object;
}
_onWebViewAdded(webView) {
const view = new AndroidWebView(this, webView);
this._webViews.set(webView.socketName, view);
this.emit(import_events.Events.AndroidDevice.WebView, view);
}
_onWebViewRemoved(socketName) {
const view = this._webViews.get(socketName);
this._webViews.delete(socketName);
if (view)
view.emit(import_events.Events.AndroidWebView.Close);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
serial() {
return this._initializer.serial;
}
model() {
return this._initializer.model;
}
webViews() {
return [...this._webViews.values()];
}
async webView(selector, options) {
const predicate = (v) => {
if (selector.pkg)
return v.pkg() === selector.pkg;
if (selector.socketName)
return v._socketName() === selector.socketName;
return false;
};
const webView = [...this._webViews.values()].find(predicate);
if (webView)
return webView;
return await this.waitForEvent("webview", { ...options, predicate });
}
async wait(selector, options = {}) {
await this._channel.wait({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
}
async fill(selector, text, options = {}) {
await this._channel.fill({ androidSelector: toSelectorChannel(selector), text, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async press(selector, key, options = {}) {
await this.tap(selector, options);
await this.input.press(key);
}
async tap(selector, options = {}) {
await this._channel.tap({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
}
async drag(selector, dest, options = {}) {
await this._channel.drag({ androidSelector: toSelectorChannel(selector), dest, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async fling(selector, direction, options = {}) {
await this._channel.fling({ androidSelector: toSelectorChannel(selector), direction, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async longTap(selector, options = {}) {
await this._channel.longTap({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
}
async pinchClose(selector, percent, options = {}) {
await this._channel.pinchClose({ androidSelector: toSelectorChannel(selector), percent, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async pinchOpen(selector, percent, options = {}) {
await this._channel.pinchOpen({ androidSelector: toSelectorChannel(selector), percent, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async scroll(selector, direction, percent, options = {}) {
await this._channel.scroll({ androidSelector: toSelectorChannel(selector), direction, percent, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async swipe(selector, direction, percent, options = {}) {
await this._channel.swipe({ androidSelector: toSelectorChannel(selector), direction, percent, ...options, timeout: this._timeoutSettings.timeout(options) });
}
async info(selector) {
return (await this._channel.info({ androidSelector: toSelectorChannel(selector) })).info;
}
async screenshot(options = {}) {
const { binary } = await this._channel.screenshot();
if (options.path)
await this._platform.fs().promises.writeFile(options.path, binary);
return binary;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close() {
try {
if (this._shouldCloseConnectionOnClose)
this._connection.close();
else
await this._channel.close();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
_didClose() {
this.emit(import_events.Events.AndroidDevice.Close, this);
}
async shell(command) {
const { result } = await this._channel.shell({ command });
return result;
}
async open(command) {
return AndroidSocket.from((await this._channel.open({ command })).socket);
}
async installApk(file, options) {
await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args });
}
async push(file, path, options) {
await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : void 0 });
}
async launchBrowser(options = {}) {
const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const result = await this._channel.launchBrowser(contextOptions);
const context = import_browserContext.BrowserContext.from(result.context);
const selectors = this._android._playwright.selectors;
selectors._contextsForSelectors.add(context);
context.once(import_events.Events.BrowserContext.Close, () => selectors._contextsForSelectors.delete(context));
await context._initializeHarFromOptions(options.recordHar);
return context;
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.AndroidDevice.Close)
waiter.rejectOnEvent(this, import_events.Events.AndroidDevice.Close, () => new import_errors.TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
class AndroidSocket extends import_channelOwner.ChannelOwner {
static from(androidDevice) {
return androidDevice._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._channel.on("data", ({ data }) => this.emit(import_events.Events.AndroidSocket.Data, data));
this._channel.on("close", () => this.emit(import_events.Events.AndroidSocket.Close));
}
async write(data) {
await this._channel.write({ data });
}
async close() {
await this._channel.close();
}
async [Symbol.asyncDispose]() {
await this.close();
}
}
async function loadFile(platform, file) {
if ((0, import_rtti.isString)(file))
return await platform.fs().promises.readFile(file);
return file;
}
class AndroidInput {
constructor(device) {
this._device = device;
}
async type(text) {
await this._device._channel.inputType({ text });
}
async press(key) {
await this._device._channel.inputPress({ key });
}
async tap(point) {
await this._device._channel.inputTap({ point });
}
async swipe(from, segments, steps) {
await this._device._channel.inputSwipe({ segments, steps });
}
async drag(from, to, steps) {
await this._device._channel.inputDrag({ from, to, steps });
}
}
function toSelectorChannel(selector) {
const {
checkable,
checked,
clazz,
clickable,
depth,
desc,
enabled,
focusable,
focused,
hasChild,
hasDescendant,
longClickable,
pkg,
res,
scrollable,
selected,
text
} = selector;
const toRegex = (value) => {
if (value === void 0)
return void 0;
if ((0, import_rtti.isRegExp)(value))
return value.source;
return "^" + value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d") + "$";
};
return {
checkable,
checked,
clazz: toRegex(clazz),
pkg: toRegex(pkg),
desc: toRegex(desc),
res: toRegex(res),
text: toRegex(text),
clickable,
depth,
enabled,
focusable,
focused,
hasChild: hasChild ? { androidSelector: toSelectorChannel(hasChild.selector) } : void 0,
hasDescendant: hasDescendant ? { androidSelector: toSelectorChannel(hasDescendant.selector), maxDepth: hasDescendant.maxDepth } : void 0,
longClickable,
scrollable,
selected
};
}
class AndroidWebView extends import_eventEmitter.EventEmitter {
constructor(device, data) {
super(device._platform);
this._device = device;
this._data = data;
}
pid() {
return this._data.pid;
}
pkg() {
return this._data.pkg;
}
_socketName() {
return this._data.socketName;
}
async page() {
if (!this._pagePromise)
this._pagePromise = this._fetchPage();
return await this._pagePromise;
}
async _fetchPage() {
const { context } = await this._device._channel.connectToWebView({ socketName: this._data.socketName });
return import_browserContext.BrowserContext.from(context).pages()[0];
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Android,
AndroidDevice,
AndroidInput,
AndroidSocket,
AndroidWebView
});
@@ -0,0 +1,137 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var api_exports = {};
__export(api_exports, {
APIRequest: () => import_fetch.APIRequest,
APIRequestContext: () => import_fetch.APIRequestContext,
APIResponse: () => import_fetch.APIResponse,
Android: () => import_android.Android,
AndroidDevice: () => import_android.AndroidDevice,
AndroidInput: () => import_android.AndroidInput,
AndroidSocket: () => import_android.AndroidSocket,
AndroidWebView: () => import_android.AndroidWebView,
Browser: () => import_browser.Browser,
BrowserContext: () => import_browserContext.BrowserContext,
BrowserType: () => import_browserType.BrowserType,
CDPSession: () => import_cdpSession.CDPSession,
Clock: () => import_clock.Clock,
ConsoleMessage: () => import_consoleMessage.ConsoleMessage,
Coverage: () => import_coverage.Coverage,
Debugger: () => import_debugger.Debugger,
Dialog: () => import_dialog.Dialog,
Download: () => import_download.Download,
Electron: () => import_electron.Electron,
ElectronApplication: () => import_electron.ElectronApplication,
ElementHandle: () => import_elementHandle.ElementHandle,
FileChooser: () => import_fileChooser.FileChooser,
Frame: () => import_frame.Frame,
FrameLocator: () => import_locator.FrameLocator,
JSHandle: () => import_jsHandle.JSHandle,
Keyboard: () => import_input.Keyboard,
Locator: () => import_locator.Locator,
Mouse: () => import_input.Mouse,
Page: () => import_page.Page,
Playwright: () => import_playwright.Playwright,
Request: () => import_network.Request,
Response: () => import_network.Response,
Route: () => import_network.Route,
Selectors: () => import_selectors.Selectors,
TimeoutError: () => import_errors.TimeoutError,
Touchscreen: () => import_input.Touchscreen,
Tracing: () => import_tracing.Tracing,
Video: () => import_video.Video,
WebError: () => import_webError.WebError,
WebSocket: () => import_network.WebSocket,
WebSocketRoute: () => import_network.WebSocketRoute,
Worker: () => import_worker.Worker
});
module.exports = __toCommonJS(api_exports);
var import_android = require("./android");
var import_browser = require("./browser");
var import_browserContext = require("./browserContext");
var import_browserType = require("./browserType");
var import_clock = require("./clock");
var import_consoleMessage = require("./consoleMessage");
var import_coverage = require("./coverage");
var import_debugger = require("./debugger");
var import_dialog = require("./dialog");
var import_download = require("./download");
var import_electron = require("./electron");
var import_locator = require("./locator");
var import_elementHandle = require("./elementHandle");
var import_fileChooser = require("./fileChooser");
var import_errors = require("./errors");
var import_frame = require("./frame");
var import_input = require("./input");
var import_jsHandle = require("./jsHandle");
var import_network = require("./network");
var import_fetch = require("./fetch");
var import_page = require("./page");
var import_selectors = require("./selectors");
var import_tracing = require("./tracing");
var import_video = require("./video");
var import_worker = require("./worker");
var import_cdpSession = require("./cdpSession");
var import_playwright = require("./playwright");
var import_webError = require("./webError");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
APIRequest,
APIRequestContext,
APIResponse,
Android,
AndroidDevice,
AndroidInput,
AndroidSocket,
AndroidWebView,
Browser,
BrowserContext,
BrowserType,
CDPSession,
Clock,
ConsoleMessage,
Coverage,
Debugger,
Dialog,
Download,
Electron,
ElectronApplication,
ElementHandle,
FileChooser,
Frame,
FrameLocator,
JSHandle,
Keyboard,
Locator,
Mouse,
Page,
Playwright,
Request,
Response,
Route,
Selectors,
TimeoutError,
Touchscreen,
Tracing,
Video,
WebError,
WebSocket,
WebSocketRoute,
Worker
});
@@ -0,0 +1,79 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var artifact_exports = {};
__export(artifact_exports, {
Artifact: () => Artifact
});
module.exports = __toCommonJS(artifact_exports);
var import_channelOwner = require("./channelOwner");
var import_stream = require("./stream");
var import_fileUtils = require("./fileUtils");
class Artifact extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
async pathAfterFinished() {
if (this._connection.isRemote())
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
return (await this._channel.pathAfterFinished()).value;
}
async saveAs(path) {
if (!this._connection.isRemote()) {
await this._channel.saveAs({ path });
return;
}
const result = await this._channel.saveAsStream();
const stream = import_stream.Stream.from(result.stream);
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, path);
await new Promise((resolve, reject) => {
stream.stream().pipe(this._platform.fs().createWriteStream(path)).on("finish", resolve).on("error", reject);
});
}
async failure() {
return (await this._channel.failure()).error || null;
}
async createReadStream() {
const result = await this._channel.stream();
const stream = import_stream.Stream.from(result.stream);
return stream.stream();
}
async readIntoBuffer() {
const stream = await this.createReadStream();
return await new Promise((resolve, reject) => {
const chunks = [];
stream.on("data", (chunk) => {
chunks.push(chunk);
});
stream.on("end", () => {
resolve(Buffer.concat(chunks));
});
stream.on("error", reject);
});
}
async cancel() {
return await this._channel.cancel();
}
async delete() {
return await this._channel.delete();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Artifact
});
@@ -0,0 +1,169 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browser_exports = {};
__export(browser_exports, {
Browser: () => Browser
});
module.exports = __toCommonJS(browser_exports);
var import_artifact = require("./artifact");
var import_browserContext = require("./browserContext");
var import_cdpSession = require("./cdpSession");
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fileUtils = require("./fileUtils");
class Browser extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._contexts = /* @__PURE__ */ new Set();
this._isConnected = true;
this._shouldCloseConnectionOnClose = false;
this._options = {};
this._name = initializer.name;
this._browserName = initializer.browserName;
this._channel.on("context", ({ context }) => this._didCreateContext(import_browserContext.BrowserContext.from(context)));
this._channel.on("close", () => this._didClose());
this._closedPromise = new Promise((f) => this.once(import_events.Events.Browser.Disconnected, f));
}
static from(browser) {
return browser._object;
}
browserType() {
return this._browserType;
}
async newContext(options = {}) {
return await this._innerNewContext(options, false);
}
async _newContextForReuse(options = {}) {
return await this._innerNewContext(options, true);
}
async _disconnectFromReusedContext(reason) {
const context = [...this._contexts].find((context2) => context2._forReuse);
if (!context)
return;
await this._instrumentation.runBeforeCloseBrowserContext(context);
for (const page of context.pages())
page._onClose();
context._onClose();
await this._channel.disconnectFromReusedContext({ reason });
}
async _innerNewContext(userOptions = {}, forReuse) {
const options = this._browserType._playwright.selectors._withSelectorOptions(userOptions);
await this._instrumentation.runBeforeCreateBrowserContext(options);
const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = import_browserContext.BrowserContext.from(response.context);
if (forReuse)
context._forReuse = true;
if (options.logger)
context._logger = options.logger;
await context._initializeHarFromOptions(options.recordHar);
await this._instrumentation.runAfterCreateBrowserContext(context);
return context;
}
_connectToBrowserType(browserType, browserOptions, logger) {
this._browserType = browserType;
this._options = browserOptions;
this._logger = logger;
for (const context of this._contexts)
this._setupBrowserContext(context);
}
_didCreateContext(context) {
context._browser = this;
this._contexts.add(context);
if (this._browserType)
this._setupBrowserContext(context);
}
_setupBrowserContext(context) {
context._logger = this._logger;
context.tracing._tracesDir = this._options.tracesDir;
this._browserType._contexts.add(context);
this._browserType._playwright.selectors._contextsForSelectors.add(context);
context.setDefaultTimeout(this._browserType._playwright._defaultContextTimeout);
context.setDefaultNavigationTimeout(this._browserType._playwright._defaultContextNavigationTimeout);
}
contexts() {
return [...this._contexts];
}
version() {
return this._initializer.version;
}
async bind(title, options = {}) {
const { endpoint } = await this._channel.startServer({ title, ...options });
return { endpoint };
}
async unbind() {
await this._channel.stopServer();
}
async newPage(options = {}) {
return await this._wrapApiCall(async () => {
const context = await this.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
context._ownerPage = page;
return page;
}, { title: "Create page" });
}
isConnected() {
return this._isConnected;
}
async newBrowserCDPSession() {
return import_cdpSession.CDPSession.from((await this._channel.newBrowserCDPSession()).session);
}
async startTracing(page, options = {}) {
this._path = options.path;
await this._channel.startTracing({ ...options, page: page ? page._channel : void 0 });
}
async stopTracing() {
const artifact = import_artifact.Artifact.from((await this._channel.stopTracing()).artifact);
const buffer = await artifact.readIntoBuffer();
await artifact.delete();
if (this._path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, this._path);
await this._platform.fs().promises.writeFile(this._path, buffer);
this._path = void 0;
}
return buffer;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close(options = {}) {
this._closeReason = options.reason;
try {
if (this._shouldCloseConnectionOnClose)
this._connection.close();
else
await this._channel.close(options);
await this._closedPromise;
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
_didClose() {
this._isConnected = false;
this.emit(import_events.Events.Browser.Disconnected, this);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Browser
});
@@ -0,0 +1,563 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserContext_exports = {};
__export(browserContext_exports, {
BrowserContext: () => BrowserContext,
prepareBrowserContextParams: () => prepareBrowserContextParams,
toClientCertificatesProtocol: () => toClientCertificatesProtocol
});
module.exports = __toCommonJS(browserContext_exports);
var import_artifact = require("./artifact");
var import_cdpSession = require("./cdpSession");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_clock = require("./clock");
var import_consoleMessage = require("./consoleMessage");
var import_debugger = require("./debugger");
var import_dialog = require("./dialog");
var import_disposable = require("./disposable");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fetch = require("./fetch");
var import_frame = require("./frame");
var import_harRouter = require("./harRouter");
var network = __toESM(require("./network"));
var import_page = require("./page");
var import_tracing = require("./tracing");
var import_waiter = require("./waiter");
var import_webError = require("./webError");
var import_worker = require("./worker");
var import_timeoutSettings = require("./timeoutSettings");
var import_fileUtils = require("./fileUtils");
var import_headers = require("../utils/isomorphic/headers");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_rtti = require("../utils/isomorphic/rtti");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class BrowserContext extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._pages = /* @__PURE__ */ new Set();
this._routes = [];
this._webSocketRoutes = [];
// Browser is null for browser contexts created outside of normal browser, e.g. android or electron.
this._browser = null;
this._bindings = /* @__PURE__ */ new Map();
this._forReuse = false;
this._serviceWorkers = /* @__PURE__ */ new Set();
this._harRecorders = /* @__PURE__ */ new Map();
this._closingStatus = "none";
this._harRouters = [];
this._options = initializer.options;
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
this.debugger = import_debugger.Debugger.from(initializer.debugger);
this.tracing = import_tracing.Tracing.from(initializer.tracing);
this.request = import_fetch.APIRequestContext.from(initializer.requestContext);
this.request._timeoutSettings = this._timeoutSettings;
this.clock = new import_clock.Clock(this);
this._channel.on("bindingCall", ({ binding }) => this._onBinding(import_page.BindingCall.from(binding)));
this._channel.on("close", () => this._onClose());
this._channel.on("page", ({ page }) => this._onPage(import_page.Page.from(page)));
this._channel.on("route", ({ route }) => this._onRoute(network.Route.from(route)));
this._channel.on("webSocketRoute", ({ webSocketRoute }) => this._onWebSocketRoute(network.WebSocketRoute.from(webSocketRoute)));
this._channel.on("serviceWorker", ({ worker }) => {
const serviceWorker = import_worker.Worker.from(worker);
serviceWorker._context = this;
this._serviceWorkers.add(serviceWorker);
this.emit(import_events.Events.BrowserContext.ServiceWorker, serviceWorker);
});
this._channel.on("console", (event) => {
const worker = import_worker.Worker.fromNullable(event.worker);
const page = import_page.Page.fromNullable(event.page);
const consoleMessage = new import_consoleMessage.ConsoleMessage(this._platform, event, page, worker);
worker?.emit(import_events.Events.Worker.Console, consoleMessage);
page?.emit(import_events.Events.Page.Console, consoleMessage);
if (worker && this._serviceWorkers.has(worker)) {
const scope = this._serviceWorkerScope(worker);
for (const page2 of this._pages) {
if (scope && page2.url().startsWith(scope))
page2.emit(import_events.Events.Page.Console, consoleMessage);
}
}
this.emit(import_events.Events.BrowserContext.Console, consoleMessage);
});
this._channel.on("pageError", ({ error, page }) => {
const pageObject = import_page.Page.from(page);
const parsedError = (0, import_errors.parseError)(error);
this.emit(import_events.Events.BrowserContext.WebError, new import_webError.WebError(pageObject, parsedError));
if (pageObject)
pageObject.emit(import_events.Events.Page.PageError, parsedError);
});
this._channel.on("dialog", ({ dialog }) => {
const dialogObject = import_dialog.Dialog.from(dialog);
let hasListeners = this.emit(import_events.Events.BrowserContext.Dialog, dialogObject);
const page = dialogObject.page();
if (page)
hasListeners = page.emit(import_events.Events.Page.Dialog, dialogObject) || hasListeners;
if (!hasListeners) {
if (dialogObject.type() === "beforeunload")
dialog.accept({}).catch(() => {
});
else
dialog.dismiss().catch(() => {
});
}
});
this._channel.on("request", ({ request, page }) => this._onRequest(network.Request.from(request), import_page.Page.fromNullable(page)));
this._channel.on("requestFailed", ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, import_page.Page.fromNullable(page)));
this._channel.on("requestFinished", (params) => this._onRequestFinished(params));
this._channel.on("response", ({ response, page }) => this._onResponse(network.Response.from(response), import_page.Page.fromNullable(page)));
this._channel.on("recorderEvent", ({ event, data, page, code }) => {
if (event === "actionAdded")
this._onRecorderEventSink?.actionAdded?.(import_page.Page.from(page), data, code);
else if (event === "actionUpdated")
this._onRecorderEventSink?.actionUpdated?.(import_page.Page.from(page), data, code);
else if (event === "signalAdded")
this._onRecorderEventSink?.signalAdded?.(import_page.Page.from(page), data);
});
this._closedPromise = new Promise((f) => this.once(import_events.Events.BrowserContext.Close, f));
this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
[import_events.Events.BrowserContext.Console, "console"],
[import_events.Events.BrowserContext.Dialog, "dialog"],
[import_events.Events.BrowserContext.Request, "request"],
[import_events.Events.BrowserContext.Response, "response"],
[import_events.Events.BrowserContext.RequestFinished, "requestFinished"],
[import_events.Events.BrowserContext.RequestFailed, "requestFailed"]
]));
}
static from(context) {
return context._object;
}
static fromNullable(context) {
return context ? BrowserContext.from(context) : null;
}
async _initializeHarFromOptions(recordHar) {
if (!recordHar)
return;
const defaultContent = recordHar.path.endsWith(".zip") ? "attach" : "embed";
await this._recordIntoHAR(recordHar.path, null, {
url: recordHar.urlFilter,
updateContent: recordHar.content ?? (recordHar.omitContent ? "omit" : defaultContent),
updateMode: recordHar.mode ?? "full"
});
}
_onPage(page) {
this._pages.add(page);
this.emit(import_events.Events.BrowserContext.Page, page);
if (page._opener && !page._opener.isClosed())
page._opener.emit(import_events.Events.Page.Popup, page);
}
_onRequest(request, page) {
this.emit(import_events.Events.BrowserContext.Request, request);
if (page)
page.emit(import_events.Events.Page.Request, request);
}
_onResponse(response, page) {
this.emit(import_events.Events.BrowserContext.Response, response);
if (page)
page.emit(import_events.Events.Page.Response, response);
}
_onRequestFailed(request, responseEndTiming, failureText, page) {
request._failureText = failureText || null;
request._setResponseEndTiming(responseEndTiming);
this.emit(import_events.Events.BrowserContext.RequestFailed, request);
if (page)
page.emit(import_events.Events.Page.RequestFailed, request);
}
_onRequestFinished(params) {
const { responseEndTiming } = params;
const request = network.Request.from(params.request);
const response = network.Response.fromNullable(params.response);
const page = import_page.Page.fromNullable(params.page);
request._setResponseEndTiming(responseEndTiming);
this.emit(import_events.Events.BrowserContext.RequestFinished, request);
if (page)
page.emit(import_events.Events.Page.RequestFinished, request);
if (response)
response._finishedPromise.resolve(null);
}
async _onRoute(route) {
route._context = this;
const page = route.request()._safePage();
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (page?._closeWasCalled || this.isClosed())
return;
if (!routeHandler.matches(route.request().url()))
continue;
const index = this._routes.indexOf(routeHandler);
if (index === -1)
continue;
if (routeHandler.willExpire())
this._routes.splice(index, 1);
const handled = await routeHandler.handle(route);
if (!this._routes.length)
this._updateInterceptionPatterns({ internal: true }).catch(() => {
});
if (handled)
return;
}
await route._innerContinue(
true
/* isFallback */
).catch(() => {
});
}
async _onWebSocketRoute(webSocketRoute) {
const routeHandler = this._webSocketRoutes.find((route) => route.matches(webSocketRoute.url()));
if (routeHandler)
await routeHandler.handle(webSocketRoute);
else
webSocketRoute.connectToServer();
}
async _onBinding(bindingCall) {
const func = this._bindings.get(bindingCall._initializer.name);
if (!func)
return;
await bindingCall.call(func);
}
_serviceWorkerScope(serviceWorker) {
try {
let url = new URL(".", serviceWorker.url()).href;
if (!url.endsWith("/"))
url += "/";
return url;
} catch {
return null;
}
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
browser() {
return this._browser;
}
pages() {
return [...this._pages];
}
isClosed() {
return this._closingStatus !== "none";
}
async newPage() {
if (this._ownerPage)
throw new Error("Please use browser.newContext()");
return import_page.Page.from((await this._channel.newPage()).page);
}
async cookies(urls) {
if (!urls)
urls = [];
if (urls && typeof urls === "string")
urls = [urls];
return (await this._channel.cookies({ urls })).cookies;
}
async addCookies(cookies) {
await this._channel.addCookies({ cookies });
}
async clearCookies(options = {}) {
await this._channel.clearCookies({
name: (0, import_rtti.isString)(options.name) ? options.name : void 0,
nameRegexSource: (0, import_rtti.isRegExp)(options.name) ? options.name.source : void 0,
nameRegexFlags: (0, import_rtti.isRegExp)(options.name) ? options.name.flags : void 0,
domain: (0, import_rtti.isString)(options.domain) ? options.domain : void 0,
domainRegexSource: (0, import_rtti.isRegExp)(options.domain) ? options.domain.source : void 0,
domainRegexFlags: (0, import_rtti.isRegExp)(options.domain) ? options.domain.flags : void 0,
path: (0, import_rtti.isString)(options.path) ? options.path : void 0,
pathRegexSource: (0, import_rtti.isRegExp)(options.path) ? options.path.source : void 0,
pathRegexFlags: (0, import_rtti.isRegExp)(options.path) ? options.path.flags : void 0
});
}
async grantPermissions(permissions, options) {
await this._channel.grantPermissions({ permissions, ...options });
}
async clearPermissions() {
await this._channel.clearPermissions();
}
async setGeolocation(geolocation) {
await this._channel.setGeolocation({ geolocation: geolocation || void 0 });
}
async setExtraHTTPHeaders(headers) {
network.validateHeaders(headers);
await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
}
async setOffline(offline) {
await this._channel.setOffline({ offline });
}
async setHTTPCredentials(httpCredentials) {
await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || void 0 });
}
async addInitScript(script, arg) {
const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, arg);
return import_disposable.DisposableObject.from((await this._channel.addInitScript({ source })).disposable);
}
async exposeBinding(name, callback, options = {}) {
const result = await this._channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback);
return import_disposable.DisposableObject.from(result.disposable);
}
async exposeFunction(name, callback) {
const result = await this._channel.exposeBinding({ name });
const binding = (source, ...args) => callback(...args);
this._bindings.set(name, binding);
return import_disposable.DisposableObject.from(result.disposable);
}
async route(url, handler, options = {}) {
this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns({ title: "Route requests" });
return new import_disposable.DisposableStub(() => this.unroute(url, handler));
}
async routeWebSocket(url, handler) {
this._webSocketRoutes.unshift(new network.WebSocketRouteHandler(this._options.baseURL, url, handler));
await this._updateWebSocketInterceptionPatterns({ title: "Route WebSockets" });
}
async _recordIntoHAR(har, page, options = {}) {
const { harId } = await this._channel.harStart({
page: page?._channel,
options: {
zip: har.endsWith(".zip"),
content: options.updateContent ?? "attach",
urlGlob: (0, import_rtti.isString)(options.url) ? options.url : void 0,
urlRegexSource: (0, import_rtti.isRegExp)(options.url) ? options.url.source : void 0,
urlRegexFlags: (0, import_rtti.isRegExp)(options.url) ? options.url.flags : void 0,
mode: options.updateMode ?? "minimal"
}
});
this._harRecorders.set(harId, { path: har, content: options.updateContent ?? "attach" });
}
async routeFromHAR(har, options = {}) {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error("Route from har is not supported in thin clients");
if (options.update) {
await this._recordIntoHAR(har, null, options);
return;
}
const harRouter = await import_harRouter.HarRouter.create(localUtils, har, options.notFound || "abort", { urlMatch: options.url });
this._harRouters.push(harRouter);
await harRouter.addContextRoute(this);
}
_disposeHarRouters() {
this._harRouters.forEach((router) => router.dispose());
this._harRouters = [];
}
async unrouteAll(options) {
await this._unrouteInternal(this._routes, [], options?.behavior);
this._disposeHarRouters();
}
async unroute(url, handler) {
const removed = [];
const remaining = [];
for (const route of this._routes) {
if ((0, import_urlMatch.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler))
removed.push(route);
else
remaining.push(route);
}
await this._unrouteInternal(removed, remaining, "default");
}
async _unrouteInternal(removed, remaining, behavior) {
this._routes = remaining;
if (behavior && behavior !== "default") {
const promises = removed.map((routeHandler) => routeHandler.stop(behavior));
await Promise.all(promises);
}
await this._updateInterceptionPatterns({ title: "Unroute requests" });
}
async _updateInterceptionPatterns(options) {
const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
await this._wrapApiCall(() => this._channel.setNetworkInterceptionPatterns({ patterns }), options);
}
async _updateWebSocketInterceptionPatterns(options) {
const patterns = network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
await this._wrapApiCall(() => this._channel.setWebSocketInterceptionPatterns({ patterns }), options);
}
_effectiveCloseReason() {
return this._closeReason || this._browser?._closeReason;
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.BrowserContext.Close)
waiter.rejectOnEvent(this, import_events.Events.BrowserContext.Close, () => new import_errors.TargetClosedError(this._effectiveCloseReason()));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async storageState(options = {}) {
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
}
return state;
}
async setStorageState(storageState) {
const state = await prepareStorageState(this._platform, storageState);
await this._channel.setStorageState({ storageState: state });
}
backgroundPages() {
return [];
}
serviceWorkers() {
return [...this._serviceWorkers];
}
async newCDPSession(page) {
if (!(page instanceof import_page.Page) && !(page instanceof import_frame.Frame))
throw new Error("page: expected Page or Frame");
const result = await this._channel.newCDPSession(page instanceof import_page.Page ? { page: page._channel } : { frame: page._channel });
return import_cdpSession.CDPSession.from(result.session);
}
_onClose() {
this._closingStatus = "closed";
this._browser?._contexts.delete(this);
this._browser?._browserType._contexts.delete(this);
this._browser?._browserType._playwright.selectors._contextsForSelectors.delete(this);
this._disposeHarRouters();
this.tracing._resetStackCounter();
this.emit(import_events.Events.BrowserContext.Close, this);
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close(options = {}) {
if (this.isClosed())
return;
this._closeReason = options.reason;
this._closingStatus = "closing";
await this.request.dispose(options);
await this._instrumentation.runBeforeCloseBrowserContext(this);
await this._wrapApiCall(async () => {
for (const [harId, harParams] of this._harRecorders) {
const har = await this._channel.harExport({ harId });
const artifact = import_artifact.Artifact.from(har.artifact);
const isCompressed = harParams.content === "attach" || harParams.path.endsWith(".zip");
const needCompressed = harParams.path.endsWith(".zip");
if (isCompressed && !needCompressed) {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error("Uncompressed har is not supported in thin clients");
await artifact.saveAs(harParams.path + ".tmp");
await localUtils.harUnzip({ zipFile: harParams.path + ".tmp", harFile: harParams.path });
} else {
await artifact.saveAs(harParams.path);
}
await artifact.delete();
}
}, { internal: true });
await this._channel.close(options);
await this._closedPromise;
}
async _enableRecorder(params, eventSink) {
if (eventSink)
this._onRecorderEventSink = eventSink;
await this._channel.enableRecorder(params);
}
async _disableRecorder() {
this._onRecorderEventSink = void 0;
await this._channel.disableRecorder();
}
async _exposeConsoleApi() {
await this._channel.exposeConsoleApi();
}
}
async function prepareStorageState(platform, storageState) {
if (typeof storageState !== "string")
return storageState;
try {
return JSON.parse(await platform.fs().promises.readFile(storageState, "utf8"));
} catch (e) {
(0, import_stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${storageState}:
` + e.message);
throw e;
}
}
async function prepareBrowserContextParams(platform, options) {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders)
network.validateHeaders(options.extraHTTPHeaders);
const contextParams = {
...options,
viewport: options.viewport === null ? void 0 : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
storageState: options.storageState ? await prepareStorageState(platform, options.storageState) : void 0,
serviceWorkers: options.serviceWorkers,
colorScheme: options.colorScheme === null ? "no-override" : options.colorScheme,
reducedMotion: options.reducedMotion === null ? "no-override" : options.reducedMotion,
forcedColors: options.forcedColors === null ? "no-override" : options.forcedColors,
contrast: options.contrast === null ? "no-override" : options.contrast,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates)
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {
dir: options.videosPath,
size: options.videoSize
};
}
if (contextParams.recordVideo && contextParams.recordVideo.dir)
contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
return contextParams;
}
function toAcceptDownloadsProtocol(acceptDownloads) {
if (acceptDownloads === void 0)
return void 0;
if (acceptDownloads)
return "accept";
return "deny";
}
async function toClientCertificatesProtocol(platform, certs) {
if (!certs)
return void 0;
const bufferizeContent = async (value, path) => {
if (value)
return value;
if (path)
return await platform.fs().promises.readFile(path);
};
return await Promise.all(certs.map(async (cert) => ({
origin: cert.origin,
cert: await bufferizeContent(cert.cert, cert.certPath),
key: await bufferizeContent(cert.key, cert.keyPath),
pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
passphrase: cert.passphrase
})));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserContext,
prepareBrowserContextParams,
toClientCertificatesProtocol
});
@@ -0,0 +1,153 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserType_exports = {};
__export(browserType_exports, {
BrowserType: () => BrowserType
});
module.exports = __toCommonJS(browserType_exports);
var import_browser = require("./browser");
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_assert = require("../utils/isomorphic/assert");
var import_headers = require("../utils/isomorphic/headers");
var import_connect = require("./connect");
var import_timeoutSettings = require("./timeoutSettings");
class BrowserType extends import_channelOwner.ChannelOwner {
constructor() {
super(...arguments);
this._contexts = /* @__PURE__ */ new Set();
}
static from(browserType) {
return browserType._object;
}
executablePath() {
if (!this._initializer.executablePath)
throw new Error("Browser is not supported on current platform");
return this._initializer.executablePath;
}
name() {
return this._initializer.name;
}
async launch(options = {}) {
(0, import_assert.assert)(!options.userDataDir, "userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead");
(0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
options = { ...this._playwright._defaultLaunchOptions, ...options };
const launchOptions = {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0,
timeout: new import_timeoutSettings.TimeoutSettings(this._platform).launchTimeout(options)
};
return await this._wrapApiCall(async () => {
const browser = import_browser.Browser.from((await this._channel.launch(launchOptions)).browser);
browser._connectToBrowserType(this, options, logger);
return browser;
});
}
async launchServer(options = {}) {
if (!this._serverLauncher)
throw new Error("Launching server is not supported");
options = { ...this._playwright._defaultLaunchOptions, ...options };
return await this._serverLauncher.launchServer(options);
}
async launchPersistentContext(userDataDir, options = {}) {
(0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
options = this._playwright.selectors._withSelectorOptions({
...this._playwright._defaultLaunchOptions,
...options
});
await this._instrumentation.runBeforeCreateBrowserContext(options);
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
const contextParams = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const persistentParams = {
...contextParams,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0,
channel: options.channel,
userDataDir: this._platform.path().isAbsolute(userDataDir) || !userDataDir ? userDataDir : this._platform.path().resolve(userDataDir),
timeout: new import_timeoutSettings.TimeoutSettings(this._platform).launchTimeout(options)
};
const context = await this._wrapApiCall(async () => {
const result = await this._channel.launchPersistentContext(persistentParams);
const browser = import_browser.Browser.from(result.browser);
browser._connectToBrowserType(this, options, logger);
const context2 = import_browserContext.BrowserContext.from(result.context);
await context2._initializeHarFromOptions(options.recordHar);
return context2;
});
await this._instrumentation.runAfterCreateBrowserContext(context);
return context;
}
async connect(optionsOrEndpoint, options) {
if (typeof optionsOrEndpoint === "string")
return await this._connect({ ...options, endpoint: optionsOrEndpoint });
(0, import_assert.assert)(optionsOrEndpoint.wsEndpoint, "options.wsEndpoint is required");
return await this._connect({ ...options, endpoint: optionsOrEndpoint.wsEndpoint });
}
async _connect(params) {
const logger = params.logger;
return await this._wrapApiCall(async () => {
const browser = await (0, import_connect.connectToBrowser)(this._playwright, { browserName: this.name(), ...params });
browser._connectToBrowserType(this, {}, logger);
return browser;
});
}
async connectOverCDP(endpointURLOrOptions, options) {
if (typeof endpointURLOrOptions === "string")
return await this._connectOverCDP(endpointURLOrOptions, options);
const endpointURL = "endpointURL" in endpointURLOrOptions ? endpointURLOrOptions.endpointURL : endpointURLOrOptions.wsEndpoint;
(0, import_assert.assert)(endpointURL, "Cannot connect over CDP without wsEndpoint.");
return await this.connectOverCDP(endpointURL, endpointURLOrOptions);
}
async _connectOverCDP(endpointURL, params = {}) {
if (this.name() !== "chromium")
throw new Error("Connecting over CDP is only supported in Chromium.");
const headers = params.headers ? (0, import_headers.headersObjectToArray)(params.headers) : void 0;
const result = await this._channel.connectOverCDP({
endpointURL,
headers,
slowMo: params.slowMo,
timeout: new import_timeoutSettings.TimeoutSettings(this._platform).timeout(params),
isLocal: params.isLocal
});
const browser = import_browser.Browser.from(result.browser);
browser._connectToBrowserType(this, {}, params.logger);
if (result.defaultContext)
await this._instrumentation.runAfterCreateBrowserContext(import_browserContext.BrowserContext.from(result.defaultContext));
return browser;
}
async _connectOverCDPTransport(transport) {
if (this.name() !== "chromium")
throw new Error("Connecting over CDP is only supported in Chromium.");
const result = await this._channel.connectOverCDPTransport({ transport });
const browser = import_browser.Browser.from(result.browser);
browser._connectToBrowserType(this, {}, void 0);
if (result.defaultContext)
await this._instrumentation.runAfterCreateBrowserContext(import_browserContext.BrowserContext.from(result.defaultContext));
return browser;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserType
});
@@ -0,0 +1,55 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var cdpSession_exports = {};
__export(cdpSession_exports, {
CDPSession: () => CDPSession
});
module.exports = __toCommonJS(cdpSession_exports);
var import_channelOwner = require("./channelOwner");
class CDPSession extends import_channelOwner.ChannelOwner {
static from(cdpSession) {
return cdpSession._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._channel.on("event", (event) => {
this.emit(event.method, event.params);
this.emit("event", event);
});
this._channel.on("close", () => {
this.emit("close", this);
});
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
async send(method, params) {
const result = await this._channel.send({ method, params });
return result.result;
}
async detach() {
return await this._channel.detach();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CDPSession
});
@@ -0,0 +1,194 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var channelOwner_exports = {};
__export(channelOwner_exports, {
ChannelOwner: () => ChannelOwner
});
module.exports = __toCommonJS(channelOwner_exports);
var import_eventEmitter = require("./eventEmitter");
var import_validator = require("../protocol/validator");
var import_protocolMetainfo = require("../utils/isomorphic/protocolMetainfo");
var import_clientStackTrace = require("./clientStackTrace");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class ChannelOwner extends import_eventEmitter.EventEmitter {
constructor(parent, type, guid, initializer) {
const connection = parent instanceof ChannelOwner ? parent._connection : parent;
super(connection._platform);
this._objects = /* @__PURE__ */ new Map();
this._eventToSubscriptionMapping = /* @__PURE__ */ new Map();
this._wasCollected = false;
this.setMaxListeners(0);
this._connection = connection;
this._type = type;
this._guid = guid;
this._parent = parent instanceof ChannelOwner ? parent : void 0;
this._instrumentation = this._connection._instrumentation;
this._connection._objects.set(guid, this);
if (this._parent) {
this._parent._objects.set(guid, this);
this._logger = this._parent._logger;
}
this._channel = this._createChannel(new import_eventEmitter.EventEmitter(connection._platform));
this._initializer = initializer;
}
_setEventToSubscriptionMapping(mapping) {
this._eventToSubscriptionMapping = mapping;
}
_updateSubscription(event, enabled) {
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
if (protocolEvent)
this._channel.updateSubscription({ event: protocolEvent, enabled }).catch(() => {
});
}
on(event, listener) {
if (!this.listenerCount(event))
this._updateSubscription(event, true);
super.on(event, listener);
return this;
}
addListener(event, listener) {
if (!this.listenerCount(event))
this._updateSubscription(event, true);
super.addListener(event, listener);
return this;
}
prependListener(event, listener) {
if (!this.listenerCount(event))
this._updateSubscription(event, true);
super.prependListener(event, listener);
return this;
}
off(event, listener) {
super.off(event, listener);
if (!this.listenerCount(event))
this._updateSubscription(event, false);
return this;
}
removeListener(event, listener) {
super.removeListener(event, listener);
if (!this.listenerCount(event))
this._updateSubscription(event, false);
return this;
}
_adopt(child) {
child._parent._objects.delete(child._guid);
this._objects.set(child._guid, child);
child._parent = this;
}
_dispose(reason) {
if (this._parent)
this._parent._objects.delete(this._guid);
this._connection._objects.delete(this._guid);
this._wasCollected = reason === "gc";
for (const object of [...this._objects.values()])
object._dispose(reason);
this._objects.clear();
}
_debugScopeState() {
return {
_guid: this._guid,
objects: Array.from(this._objects.values()).map((o) => o._debugScopeState())
};
}
_validatorToWireContext() {
return {
tChannelImpl: tChannelImplToWire,
binary: this._connection.rawBuffers() ? "buffer" : "toBase64",
isUnderTest: () => this._platform.isUnderTest()
};
}
_createChannel(base) {
const channel = new Proxy(base, {
get: (obj, prop) => {
if (typeof prop === "string") {
const validator = (0, import_validator.maybeFindValidator)(this._type, prop, "Params");
const { internal } = (0, import_protocolMetainfo.getMetainfo)({ type: this._type, method: prop }) || {};
if (validator) {
return async (params) => {
return await this._wrapApiCall(async (apiZone) => {
const validatedParams = validator(params, "", this._validatorToWireContext());
if (!apiZone.internal && !apiZone.reported) {
apiZone.reported = true;
this._instrumentation.onApiCallBegin(apiZone, { type: this._type, method: prop, params });
logApiCall(this._platform, this._logger, `=> ${apiZone.apiName} started`);
return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone);
}
return await this._connection.sendMessageToServer(this, prop, validatedParams, { internal: true });
}, { internal });
};
}
}
return obj[prop];
}
});
channel._object = this;
return channel;
}
async _wrapApiCall(func, options) {
const logger = this._logger;
const existingApiZone = this._platform.zones.current().data();
if (existingApiZone)
return await func(existingApiZone);
const stackTrace = (0, import_clientStackTrace.captureLibraryStackTrace)(this._platform);
const apiZone = { title: options?.title, apiName: stackTrace.apiName, frames: stackTrace.frames, internal: options?.internal ?? false, reported: false, userData: void 0, stepId: void 0 };
try {
const result = await this._platform.zones.current().push(apiZone).run(async () => await func(apiZone));
if (!options?.internal) {
logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
this._instrumentation.onApiCallEnd(apiZone);
}
return result;
} catch (e) {
const innerError = (this._platform.showInternalStackFrames() || this._platform.isUnderTest()) && e.stack ? "\n<inner error>\n" + e.stack : "";
if (apiZone.apiName && !apiZone.apiName.includes("<anonymous>"))
e.message = apiZone.apiName + ": " + e.message;
const stackFrames = "\n" + (0, import_stackTrace.stringifyStackFrames)(stackTrace.frames).join("\n") + innerError;
if (stackFrames.trim())
e.stack = e.message + stackFrames;
else
e.stack = "";
if (!options?.internal) {
apiZone.error = e;
logApiCall(this._platform, logger, `<= ${apiZone.apiName} failed`);
this._instrumentation.onApiCallEnd(apiZone);
}
throw e;
}
}
toJSON() {
return {
_type: this._type,
_guid: this._guid
};
}
}
function logApiCall(platform, logger, message) {
if (logger && logger.isEnabled("api", "info"))
logger.log("api", "info", message, [], { color: "cyan" });
platform.log("api", message);
}
function tChannelImplToWire(names, arg, path, context) {
if (arg._object instanceof ChannelOwner && (names === "*" || names.includes(arg._object._type)))
return { guid: arg._object._guid };
throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ChannelOwner
});
@@ -0,0 +1,64 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clientHelper_exports = {};
__export(clientHelper_exports, {
addSourceUrlToScript: () => addSourceUrlToScript,
envObjectToArray: () => envObjectToArray,
evaluationScript: () => evaluationScript
});
module.exports = __toCommonJS(clientHelper_exports);
var import_rtti = require("../utils/isomorphic/rtti");
function envObjectToArray(env) {
const result = [];
for (const name in env) {
if (!Object.is(env[name], void 0))
result.push({ name, value: String(env[name]) });
}
return result;
}
async function evaluationScript(platform, fun, arg, addSourceUrl = true) {
if (typeof fun === "function") {
const source = fun.toString();
const argString = Object.is(arg, void 0) ? "undefined" : JSON.stringify(arg);
return `(${source})(${argString})`;
}
if (arg !== void 0)
throw new Error("Cannot evaluate a string with arguments");
if ((0, import_rtti.isString)(fun))
return fun;
if (fun.content !== void 0)
return fun.content;
if (fun.path !== void 0) {
let source = await platform.fs().promises.readFile(fun.path, "utf8");
if (addSourceUrl)
source = addSourceUrlToScript(source, fun.path);
return source;
}
throw new Error("Either path or content property must be present");
}
function addSourceUrlToScript(source, path) {
return `${source}
//# sourceURL=${path.replace(/\n/g, "")}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
addSourceUrlToScript,
envObjectToArray,
evaluationScript
});
@@ -0,0 +1,55 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clientInstrumentation_exports = {};
__export(clientInstrumentation_exports, {
createInstrumentation: () => createInstrumentation
});
module.exports = __toCommonJS(clientInstrumentation_exports);
function createInstrumentation() {
const listeners = [];
return new Proxy({}, {
get: (obj, prop) => {
if (typeof prop !== "string")
return obj[prop];
if (prop === "addListener")
return (listener) => listeners.push(listener);
if (prop === "removeListener")
return (listener) => listeners.splice(listeners.indexOf(listener), 1);
if (prop === "removeAllListeners")
return () => listeners.splice(0, listeners.length);
if (prop.startsWith("run")) {
return async (...params) => {
for (const listener of listeners)
await listener[prop]?.(...params);
};
}
if (prop.startsWith("on")) {
return (...params) => {
for (const listener of listeners)
listener[prop]?.(...params);
};
}
return obj[prop];
}
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createInstrumentation
});
@@ -0,0 +1,69 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clientStackTrace_exports = {};
__export(clientStackTrace_exports, {
captureLibraryStackTrace: () => captureLibraryStackTrace
});
module.exports = __toCommonJS(clientStackTrace_exports);
var import_stackTrace = require("../utils/isomorphic/stackTrace");
function captureLibraryStackTrace(platform) {
const stack = (0, import_stackTrace.captureRawStack)();
let parsedFrames = stack.map((line) => {
const frame = (0, import_stackTrace.parseStackFrame)(line, platform.pathSeparator, platform.showInternalStackFrames());
if (!frame || !frame.file)
return null;
const isPlaywrightLibrary = !!platform.coreDir && frame.file.startsWith(platform.coreDir);
const parsed = {
frame,
frameText: line,
isPlaywrightLibrary
};
return parsed;
}).filter(Boolean);
let apiName = "";
for (let i = 0; i < parsedFrames.length - 1; i++) {
const parsedFrame = parsedFrames[i];
if (parsedFrame.isPlaywrightLibrary && !parsedFrames[i + 1].isPlaywrightLibrary) {
apiName = apiName || normalizeAPIName(parsedFrame.frame.function);
break;
}
}
function normalizeAPIName(name) {
if (!name)
return "";
const match = name.match(/(API|JS|CDP|[A-Z])(.*)/);
if (!match)
return name;
return match[1].toLowerCase() + match[2];
}
const filterPrefixes = platform.boxedStackPrefixes();
parsedFrames = parsedFrames.filter((f) => {
if (filterPrefixes.some((prefix) => f.frame.file.startsWith(prefix)))
return false;
return true;
});
return {
frames: parsedFrames.map((p) => p.frame),
apiName
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
captureLibraryStackTrace
});
@@ -0,0 +1,68 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clock_exports = {};
__export(clock_exports, {
Clock: () => Clock
});
module.exports = __toCommonJS(clock_exports);
class Clock {
constructor(browserContext) {
this._browserContext = browserContext;
}
async install(options = {}) {
await this._browserContext._channel.clockInstall(options.time !== void 0 ? parseTime(options.time) : {});
}
async fastForward(ticks) {
await this._browserContext._channel.clockFastForward(parseTicks(ticks));
}
async pauseAt(time) {
await this._browserContext._channel.clockPauseAt(parseTime(time));
}
async resume() {
await this._browserContext._channel.clockResume({});
}
async runFor(ticks) {
await this._browserContext._channel.clockRunFor(parseTicks(ticks));
}
async setFixedTime(time) {
await this._browserContext._channel.clockSetFixedTime(parseTime(time));
}
async setSystemTime(time) {
await this._browserContext._channel.clockSetSystemTime(parseTime(time));
}
}
function parseTime(time) {
if (typeof time === "number")
return { timeNumber: time };
if (typeof time === "string")
return { timeString: time };
if (!isFinite(time.getTime()))
throw new Error(`Invalid date: ${time}`);
return { timeNumber: time.getTime() };
}
function parseTicks(ticks) {
return {
ticksNumber: typeof ticks === "number" ? ticks : void 0,
ticksString: typeof ticks === "string" ? ticks : void 0
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Clock
});
@@ -0,0 +1,143 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var connect_exports = {};
__export(connect_exports, {
connectToBrowser: () => connectToBrowser,
connectToEndpoint: () => connectToEndpoint
});
module.exports = __toCommonJS(connect_exports);
var import_time = require("../utils/isomorphic/time");
var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
var import_browser = require("./browser");
var import_connection = require("./connection");
var import_events = require("./events");
async function connectToBrowser(playwright, params) {
const deadline = params.timeout ? (0, import_time.monotonicTime)() + params.timeout : 0;
const nameParam = params.browserName ? { "x-playwright-browser": params.browserName } : {};
const headers = { ...nameParam, ...params.headers };
const connectParams = {
endpoint: params.endpoint,
headers,
exposeNetwork: params.exposeNetwork,
slowMo: params.slowMo,
timeout: params.timeout || 0
};
if (params.__testHookRedirectPortForwarding)
connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
const connection = await connectToEndpoint(playwright._connection, connectParams);
let browser;
connection.on("close", () => {
for (const context of browser?.contexts() || []) {
for (const page of context.pages())
page._onClose();
context._onClose();
}
setTimeout(() => browser?._didClose(), 0);
});
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
if (params.__testHookBeforeCreateBrowser)
await params.__testHookBeforeCreateBrowser();
const playwright2 = await connection.initializePlaywright();
if (!playwright2._initializer.preLaunchedBrowser) {
connection.close();
throw new Error("Malformed endpoint. Did you use BrowserType.launchServer method?");
}
playwright2.selectors = playwright2.selectors;
browser = import_browser.Browser.from(playwright2._initializer.preLaunchedBrowser);
browser._shouldCloseConnectionOnClose = true;
browser.on(import_events.Events.Browser.Disconnected, () => connection.close());
return browser;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
connection.close();
throw new Error(`Timeout ${params.timeout}ms exceeded`);
}
}
async function connectToEndpoint(parentConnection, params) {
const localUtils = parentConnection.localUtils();
const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport();
const connectHeaders = await transport.connect(params);
const connection = new import_connection.Connection(parentConnection._platform, localUtils, parentConnection._instrumentation, connectHeaders);
connection.markAsRemote();
connection.on("close", () => transport.close());
let closeError;
const onTransportClosed = (reason) => {
connection.close(reason || closeError);
};
transport.onClose((reason) => onTransportClosed(reason));
connection.onmessage = (message) => transport.send(message).catch(() => onTransportClosed());
transport.onMessage((message) => {
try {
connection.dispatch(message);
} catch (e) {
closeError = String(e);
transport.close().catch(() => {
});
}
});
return connection;
}
class JsonPipeTransport {
constructor(owner) {
this._owner = owner;
}
async connect(params) {
const { pipe, headers: connectHeaders } = await this._owner._channel.connect(params);
this._pipe = pipe;
return connectHeaders;
}
async send(message) {
await this._pipe.send({ message });
}
onMessage(callback) {
this._pipe.on("message", ({ message }) => callback(message));
}
onClose(callback) {
this._pipe.on("closed", ({ reason }) => callback(reason));
}
async close() {
await this._pipe.close().catch(() => {
});
}
}
class WebSocketTransport {
async connect(params) {
this._ws = new window.WebSocket(params.endpoint);
return [];
}
async send(message) {
this._ws.send(JSON.stringify(message));
}
onMessage(callback) {
this._ws.addEventListener("message", (event) => callback(JSON.parse(event.data)));
}
onClose(callback) {
this._ws.addEventListener("close", () => callback());
}
async close() {
this._ws.close();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
connectToBrowser,
connectToEndpoint
});
@@ -0,0 +1,322 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var connection_exports = {};
__export(connection_exports, {
Connection: () => Connection
});
module.exports = __toCommonJS(connection_exports);
var import_eventEmitter = require("./eventEmitter");
var import_android = require("./android");
var import_artifact = require("./artifact");
var import_browser = require("./browser");
var import_browserContext = require("./browserContext");
var import_browserType = require("./browserType");
var import_cdpSession = require("./cdpSession");
var import_channelOwner = require("./channelOwner");
var import_clientInstrumentation = require("./clientInstrumentation");
var import_debugger = require("./debugger");
var import_dialog = require("./dialog");
var import_disposable = require("./disposable");
var import_electron = require("./electron");
var import_elementHandle = require("./elementHandle");
var import_errors = require("./errors");
var import_fetch = require("./fetch");
var import_frame = require("./frame");
var import_jsHandle = require("./jsHandle");
var import_jsonPipe = require("./jsonPipe");
var import_localUtils = require("./localUtils");
var import_network = require("./network");
var import_page = require("./page");
var import_playwright = require("./playwright");
var import_stream = require("./stream");
var import_tracing = require("./tracing");
var import_worker = require("./worker");
var import_writableStream = require("./writableStream");
var import_validator = require("../protocol/validator");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class Root extends import_channelOwner.ChannelOwner {
constructor(connection) {
super(connection, "Root", "", {});
}
async initialize() {
return import_playwright.Playwright.from((await this._channel.initialize({
sdkLanguage: "javascript"
})).playwright);
}
}
class DummyChannelOwner extends import_channelOwner.ChannelOwner {
}
class Connection extends import_eventEmitter.EventEmitter {
constructor(platform, localUtils, instrumentation, headers = []) {
super(platform);
this._objects = /* @__PURE__ */ new Map();
this.onmessage = (message) => {
};
this._lastId = 0;
this._callbacks = /* @__PURE__ */ new Map();
this._isRemote = false;
this._rawBuffers = false;
this._tracingCount = 0;
this._instrumentation = instrumentation || (0, import_clientInstrumentation.createInstrumentation)();
this._localUtils = localUtils;
this._rootObject = new Root(this);
this.headers = headers;
}
markAsRemote() {
this._isRemote = true;
}
isRemote() {
return this._isRemote;
}
useRawBuffers() {
this._rawBuffers = true;
}
rawBuffers() {
return this._rawBuffers;
}
localUtils() {
return this._localUtils;
}
async initializePlaywright() {
return await this._rootObject.initialize();
}
getObjectWithKnownName(guid) {
return this._objects.get(guid);
}
setIsTracing(isTracing) {
if (isTracing)
this._tracingCount++;
else
this._tracingCount--;
}
async sendMessageToServer(object, method, params, options) {
if (this._closedError)
throw this._closedError;
if (object._wasCollected)
throw new Error("The object has been collected to prevent unbounded heap growth.");
const guid = object._guid;
const type = object._type;
const id = ++this._lastId;
const message = { id, guid, method, params };
if (this._platform.isLogEnabled("channel")) {
this._platform.log("channel", "SEND> " + JSON.stringify(message));
}
const location = options.frames?.[0] ? { file: options.frames[0].file, line: options.frames[0].line, column: options.frames[0].column } : void 0;
const metadata = { title: options.title, location, internal: options.internal, stepId: options.stepId };
if (this._tracingCount && options.frames && type !== "LocalUtils")
this._localUtils?.addStackToTracingNoReply({ callData: { stack: options.frames ?? [], id } }).catch(() => {
});
this._platform.zones.empty.run(() => this.onmessage({ ...message, metadata }));
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, title: options.title, type, method }));
}
_validatorFromWireContext() {
return {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._rawBuffers ? "buffer" : "fromBase64",
isUnderTest: () => this._platform.isUnderTest()
};
}
dispatch(message) {
if (this._closedError)
return;
const { id, guid, method, params, result, error, log } = message;
if (id) {
if (this._platform.isLogEnabled("channel"))
this._platform.log("channel", "<RECV " + JSON.stringify(message));
const callback = this._callbacks.get(id);
if (!callback)
throw new Error(`Cannot find command to respond: ${id}`);
this._callbacks.delete(id);
if (error && !result) {
const parsedError = (0, import_errors.parseError)(error);
(0, import_stackTrace.rewriteErrorMessage)(parsedError, parsedError.message + formatCallLog(this._platform, log));
callback.reject(parsedError);
} else {
const validator2 = (0, import_validator.findValidator)(callback.type, callback.method, "Result");
callback.resolve(validator2(result, "", this._validatorFromWireContext()));
}
return;
}
if (this._platform.isLogEnabled("channel"))
this._platform.log("channel", "<EVENT " + JSON.stringify(message));
if (method === "__create__") {
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
return;
}
const object = this._objects.get(guid);
if (!object)
throw new Error(`Cannot find object to "${method}": ${guid}`);
if (method === "__adopt__") {
const child = this._objects.get(params.guid);
if (!child)
throw new Error(`Unknown new child: ${params.guid}`);
object._adopt(child);
return;
}
if (method === "__dispose__") {
object._dispose(params.reason);
return;
}
const validator = (0, import_validator.findValidator)(object._type, method, "Event");
object._channel.emit(method, validator(params, "", this._validatorFromWireContext()));
}
close(cause) {
if (this._closedError)
return;
this._closedError = new import_errors.TargetClosedError(cause);
for (const callback of this._callbacks.values())
callback.reject(this._closedError);
this._callbacks.clear();
this.emit("close");
}
_tChannelImplFromWire(names, arg, path, context) {
if (arg && typeof arg === "object" && typeof arg.guid === "string") {
const object = this._objects.get(arg.guid);
if (!object)
throw new Error(`Object with guid ${arg.guid} was not bound in the connection`);
if (names !== "*" && !names.includes(object._type))
throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
return object._channel;
}
throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
}
_createRemoteObject(parentGuid, type, guid, initializer) {
const parent = this._objects.get(parentGuid);
if (!parent)
throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
let result;
const validator = (0, import_validator.findValidator)(type, "", "Initializer");
initializer = validator(initializer, "", this._validatorFromWireContext());
switch (type) {
case "Android":
result = new import_android.Android(parent, type, guid, initializer);
break;
case "AndroidSocket":
result = new import_android.AndroidSocket(parent, type, guid, initializer);
break;
case "AndroidDevice":
result = new import_android.AndroidDevice(parent, type, guid, initializer);
break;
case "APIRequestContext":
result = new import_fetch.APIRequestContext(parent, type, guid, initializer);
break;
case "Artifact":
result = new import_artifact.Artifact(parent, type, guid, initializer);
break;
case "BindingCall":
result = new import_page.BindingCall(parent, type, guid, initializer);
break;
case "Browser":
result = new import_browser.Browser(parent, type, guid, initializer);
break;
case "BrowserContext":
result = new import_browserContext.BrowserContext(parent, type, guid, initializer);
break;
case "BrowserType":
result = new import_browserType.BrowserType(parent, type, guid, initializer);
break;
case "CDPSession":
result = new import_cdpSession.CDPSession(parent, type, guid, initializer);
break;
case "Debugger":
result = new import_debugger.Debugger(parent, type, guid, initializer);
break;
case "Dialog":
result = new import_dialog.Dialog(parent, type, guid, initializer);
break;
case "Disposable":
result = new import_disposable.DisposableObject(parent, type, guid, initializer);
break;
case "Electron":
result = new import_electron.Electron(parent, type, guid, initializer);
break;
case "ElectronApplication":
result = new import_electron.ElectronApplication(parent, type, guid, initializer);
break;
case "ElementHandle":
result = new import_elementHandle.ElementHandle(parent, type, guid, initializer);
break;
case "Frame":
result = new import_frame.Frame(parent, type, guid, initializer);
break;
case "JSHandle":
result = new import_jsHandle.JSHandle(parent, type, guid, initializer);
break;
case "JsonPipe":
result = new import_jsonPipe.JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
result = new import_localUtils.LocalUtils(parent, type, guid, initializer);
if (!this._localUtils)
this._localUtils = result;
break;
case "Page":
result = new import_page.Page(parent, type, guid, initializer);
break;
case "Playwright":
result = new import_playwright.Playwright(parent, type, guid, initializer);
break;
case "Request":
result = new import_network.Request(parent, type, guid, initializer);
break;
case "Response":
result = new import_network.Response(parent, type, guid, initializer);
break;
case "Route":
result = new import_network.Route(parent, type, guid, initializer);
break;
case "Stream":
result = new import_stream.Stream(parent, type, guid, initializer);
break;
case "SocksSupport":
result = new DummyChannelOwner(parent, type, guid, initializer);
break;
case "Tracing":
result = new import_tracing.Tracing(parent, type, guid, initializer);
break;
case "WebSocket":
result = new import_network.WebSocket(parent, type, guid, initializer);
break;
case "WebSocketRoute":
result = new import_network.WebSocketRoute(parent, type, guid, initializer);
break;
case "Worker":
result = new import_worker.Worker(parent, type, guid, initializer);
break;
case "WritableStream":
result = new import_writableStream.WritableStream(parent, type, guid, initializer);
break;
default:
throw new Error("Missing type " + type);
}
return result;
}
}
function formatCallLog(platform, log) {
if (!log || !log.some((l) => !!l))
return "";
return `
Call log:
${platform.colors.dim(log.join("\n"))}
`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Connection
});
@@ -0,0 +1,61 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var consoleMessage_exports = {};
__export(consoleMessage_exports, {
ConsoleMessage: () => ConsoleMessage
});
module.exports = __toCommonJS(consoleMessage_exports);
var import_jsHandle = require("./jsHandle");
class ConsoleMessage {
constructor(platform, event, page, worker) {
this._page = page;
this._worker = worker;
this._event = event;
if (platform.inspectCustom)
this[platform.inspectCustom] = () => this._inspect();
}
worker() {
return this._worker;
}
page() {
return this._page;
}
type() {
return this._event.type;
}
text() {
return this._event.text;
}
args() {
return this._event.args.map(import_jsHandle.JSHandle.from);
}
location() {
return this._event.location;
}
timestamp() {
return this._event.timestamp;
}
_inspect() {
return this.text();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ConsoleMessage
});
@@ -0,0 +1,44 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var coverage_exports = {};
__export(coverage_exports, {
Coverage: () => Coverage
});
module.exports = __toCommonJS(coverage_exports);
class Coverage {
constructor(channel) {
this._channel = channel;
}
async startJSCoverage(options = {}) {
await this._channel.startJSCoverage(options);
}
async stopJSCoverage() {
return (await this._channel.stopJSCoverage()).entries;
}
async startCSSCoverage(options = {}) {
await this._channel.startCSSCoverage(options);
}
async stopCSSCoverage() {
return (await this._channel.stopCSSCoverage()).entries;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Coverage
});
@@ -0,0 +1,57 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var debugger_exports = {};
__export(debugger_exports, {
Debugger: () => Debugger
});
module.exports = __toCommonJS(debugger_exports);
var import_channelOwner = require("./channelOwner");
var import_events = require("./events");
class Debugger extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._pausedDetails = null;
this._channel.on("pausedStateChanged", ({ pausedDetails }) => {
this._pausedDetails = pausedDetails ?? null;
this.emit(import_events.Events.Debugger.PausedStateChanged);
});
}
static from(channel) {
return channel._object;
}
async requestPause() {
await this._channel.requestPause();
}
async resume() {
await this._channel.resume();
}
async next() {
await this._channel.next();
}
async runTo(location) {
await this._channel.runTo({ location });
}
pausedDetails() {
return this._pausedDetails;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Debugger
});
@@ -0,0 +1,63 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var dialog_exports = {};
__export(dialog_exports, {
Dialog: () => Dialog
});
module.exports = __toCommonJS(dialog_exports);
var import_channelOwner = require("./channelOwner");
var import_page = require("./page");
var import_errors = require("./errors");
class Dialog extends import_channelOwner.ChannelOwner {
static from(dialog) {
return dialog._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._page = import_page.Page.fromNullable(initializer.page);
}
page() {
return this._page;
}
type() {
return this._initializer.type;
}
message() {
return this._initializer.message;
}
defaultValue() {
return this._initializer.defaultValue;
}
async accept(promptText) {
await this._channel.accept({ promptText });
}
async dismiss() {
try {
await this._channel.dismiss();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Dialog
});
@@ -0,0 +1,76 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var disposable_exports = {};
__export(disposable_exports, {
DisposableObject: () => DisposableObject,
DisposableStub: () => DisposableStub,
disposeAll: () => disposeAll
});
module.exports = __toCommonJS(disposable_exports);
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
class DisposableObject extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
try {
await this._channel.dispose();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
}
class DisposableStub {
constructor(dispose) {
this._dispose = dispose;
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
if (!this._dispose)
return;
try {
const dispose = this._dispose;
this._dispose = void 0;
await dispose();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
}
async function disposeAll(disposables) {
const copy = [...disposables];
disposables.length = 0;
await Promise.all(copy.map((d) => d.dispose()));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DisposableObject,
DisposableStub,
disposeAll
});
@@ -0,0 +1,62 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var download_exports = {};
__export(download_exports, {
Download: () => Download
});
module.exports = __toCommonJS(download_exports);
class Download {
constructor(page, url, suggestedFilename, artifact) {
this._page = page;
this._url = url;
this._suggestedFilename = suggestedFilename;
this._artifact = artifact;
}
page() {
return this._page;
}
url() {
return this._url;
}
suggestedFilename() {
return this._suggestedFilename;
}
async path() {
return await this._artifact.pathAfterFinished();
}
async saveAs(path) {
return await this._artifact.saveAs(path);
}
async failure() {
return await this._artifact.failure();
}
async createReadStream() {
return await this._artifact.createReadStream();
}
async cancel() {
return await this._artifact.cancel();
}
async delete() {
return await this._artifact.delete();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Download
});
@@ -0,0 +1,139 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var electron_exports = {};
__export(electron_exports, {
Electron: () => Electron,
ElectronApplication: () => ElectronApplication
});
module.exports = __toCommonJS(electron_exports);
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_consoleMessage = require("./consoleMessage");
var import_errors = require("./errors");
var import_events = require("./events");
var import_jsHandle = require("./jsHandle");
var import_waiter = require("./waiter");
var import_timeoutSettings = require("./timeoutSettings");
class Electron extends import_channelOwner.ChannelOwner {
static from(electron) {
return electron._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
async launch(options = {}) {
options = this._playwright.selectors._withSelectorOptions(options);
const params = {
...await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options),
env: (0, import_clientHelper.envObjectToArray)(options.env ? options.env : this._platform.env),
tracesDir: options.tracesDir,
artifactsDir: options.artifactsDir,
timeout: new import_timeoutSettings.TimeoutSettings(this._platform).launchTimeout(options)
};
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
this._playwright.selectors._contextsForSelectors.add(app._context);
app.once(import_events.Events.ElectronApplication.Close, () => this._playwright.selectors._contextsForSelectors.delete(app._context));
await app._context._initializeHarFromOptions(options.recordHar);
app._context.tracing._tracesDir = options.tracesDir;
return app;
}
}
class ElectronApplication extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._windows = /* @__PURE__ */ new Set();
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
this._context = import_browserContext.BrowserContext.from(initializer.context);
for (const page of this._context._pages)
this._onPage(page);
this._context.on(import_events.Events.BrowserContext.Page, (page) => this._onPage(page));
this._channel.on("close", () => {
this.emit(import_events.Events.ElectronApplication.Close);
});
this._channel.on("console", (event) => this.emit(import_events.Events.ElectronApplication.Console, new import_consoleMessage.ConsoleMessage(this._platform, event, null, null)));
this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
[import_events.Events.ElectronApplication.Console, "console"]
]));
}
static from(electronApplication) {
return electronApplication._object;
}
process() {
return this._connection.toImpl?.(this)?.process();
}
_onPage(page) {
this._windows.add(page);
this.emit(import_events.Events.ElectronApplication.Window, page);
page.once(import_events.Events.Page.Close, () => this._windows.delete(page));
}
windows() {
return [...this._windows];
}
async firstWindow(options) {
if (this._windows.size)
return this._windows.values().next().value;
return await this.waitForEvent("window", options);
}
context() {
return this._context;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close() {
try {
await this._context.close();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.ElectronApplication.Close)
waiter.rejectOnEvent(this, import_events.Events.ElectronApplication.Close, () => new import_errors.TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async browserWindow(page) {
const result = await this._channel.browserWindow({ page: page._channel });
return import_jsHandle.JSHandle.from(result.handle);
}
async evaluate(pageFunction, arg) {
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async evaluateHandle(pageFunction, arg) {
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return import_jsHandle.JSHandle.from(result.handle);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Electron,
ElectronApplication
});
@@ -0,0 +1,281 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var elementHandle_exports = {};
__export(elementHandle_exports, {
ElementHandle: () => ElementHandle,
convertInputFiles: () => convertInputFiles,
convertSelectOptionValues: () => convertSelectOptionValues,
determineScreenshotType: () => determineScreenshotType
});
module.exports = __toCommonJS(elementHandle_exports);
var import_frame = require("./frame");
var import_jsHandle = require("./jsHandle");
var import_assert = require("../utils/isomorphic/assert");
var import_fileUtils = require("./fileUtils");
var import_rtti = require("../utils/isomorphic/rtti");
var import_writableStream = require("./writableStream");
var import_mimeType = require("../utils/isomorphic/mimeType");
class ElementHandle extends import_jsHandle.JSHandle {
static from(handle) {
return handle._object;
}
static fromNullable(handle) {
return handle ? ElementHandle.from(handle) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._frame = parent;
this._elementChannel = this._channel;
}
asElement() {
return this;
}
async ownerFrame() {
return import_frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
}
async contentFrame() {
return import_frame.Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
}
async getAttribute(name) {
const value = (await this._elementChannel.getAttribute({ name })).value;
return value === void 0 ? null : value;
}
async inputValue() {
return (await this._elementChannel.inputValue()).value;
}
async textContent() {
const value = (await this._elementChannel.textContent()).value;
return value === void 0 ? null : value;
}
async innerText() {
return (await this._elementChannel.innerText()).value;
}
async innerHTML() {
return (await this._elementChannel.innerHTML()).value;
}
async isChecked() {
return (await this._elementChannel.isChecked()).value;
}
async isDisabled() {
return (await this._elementChannel.isDisabled()).value;
}
async isEditable() {
return (await this._elementChannel.isEditable()).value;
}
async isEnabled() {
return (await this._elementChannel.isEnabled()).value;
}
async isHidden() {
return (await this._elementChannel.isHidden()).value;
}
async isVisible() {
return (await this._elementChannel.isVisible()).value;
}
async dispatchEvent(type, eventInit = {}) {
await this._elementChannel.dispatchEvent({ type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit) });
}
async scrollIntoViewIfNeeded(options = {}) {
await this._elementChannel.scrollIntoViewIfNeeded({ ...options, timeout: this._frame._timeout(options) });
}
async hover(options = {}) {
await this._elementChannel.hover({ ...options, timeout: this._frame._timeout(options) });
}
async click(options = {}) {
return await this._elementChannel.click({ ...options, timeout: this._frame._timeout(options) });
}
async dblclick(options = {}) {
return await this._elementChannel.dblclick({ ...options, timeout: this._frame._timeout(options) });
}
async tap(options = {}) {
return await this._elementChannel.tap({ ...options, timeout: this._frame._timeout(options) });
}
async selectOption(values, options = {}) {
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options, timeout: this._frame._timeout(options) });
return result.values;
}
async fill(value, options = {}) {
return await this._elementChannel.fill({ value, ...options, timeout: this._frame._timeout(options) });
}
async selectText(options = {}) {
await this._elementChannel.selectText({ ...options, timeout: this._frame._timeout(options) });
}
async setInputFiles(files, options = {}) {
const frame = await this.ownerFrame();
if (!frame)
throw new Error("Cannot set input files to detached element");
const converted = await convertInputFiles(this._platform, files, frame.page().context());
await this._elementChannel.setInputFiles({ ...converted, ...options, timeout: this._frame._timeout(options) });
}
async focus() {
await this._elementChannel.focus();
}
async type(text, options = {}) {
await this._elementChannel.type({ text, ...options, timeout: this._frame._timeout(options) });
}
async press(key, options = {}) {
await this._elementChannel.press({ key, ...options, timeout: this._frame._timeout(options) });
}
async check(options = {}) {
return await this._elementChannel.check({ ...options, timeout: this._frame._timeout(options) });
}
async uncheck(options = {}) {
return await this._elementChannel.uncheck({ ...options, timeout: this._frame._timeout(options) });
}
async setChecked(checked, options) {
if (checked)
await this.check(options);
else
await this.uncheck(options);
}
async boundingBox() {
const value = (await this._elementChannel.boundingBox()).value;
return value === void 0 ? null : value;
}
async screenshot(options = {}) {
const mask = options.mask;
const copy = { ...options, mask: void 0, timeout: this._frame._timeout(options) };
if (!copy.type)
copy.type = determineScreenshotType(options);
if (mask) {
copy.mask = mask.map((locator) => ({
frame: locator._frame._channel,
selector: locator._selector
}));
}
const result = await this._elementChannel.screenshot(copy);
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, result.binary);
}
return result.binary;
}
async $(selector) {
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element);
}
async $$(selector) {
const result = await this._elementChannel.querySelectorAll({ selector });
return result.elements.map((h) => ElementHandle.from(h));
}
async $eval(selector, pageFunction, arg) {
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $$eval(selector, pageFunction, arg) {
const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async waitForElementState(state, options = {}) {
return await this._elementChannel.waitForElementState({ state, ...options, timeout: this._frame._timeout(options) });
}
async waitForSelector(selector, options = {}) {
const result = await this._elementChannel.waitForSelector({ selector, ...options, timeout: this._frame._timeout(options) });
return ElementHandle.fromNullable(result.element);
}
}
function convertSelectOptionValues(values) {
if (values === null)
return {};
if (!Array.isArray(values))
values = [values];
if (!values.length)
return {};
for (let i = 0; i < values.length; i++)
(0, import_assert.assert)(values[i] !== null, `options[${i}]: expected object, got null`);
if (values[0] instanceof ElementHandle)
return { elements: values.map((v) => v._elementChannel) };
if ((0, import_rtti.isString)(values[0]))
return { options: values.map((valueOrLabel) => ({ valueOrLabel })) };
return { options: values };
}
function filePayloadExceedsSizeLimit(payloads) {
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= import_fileUtils.fileUploadSizeLimit;
}
async function resolvePathsAndDirectoryForInputFiles(platform, items) {
let localPaths;
let localDirectory;
for (const item of items) {
const stat = await platform.fs().promises.stat(item);
if (stat.isDirectory()) {
if (localDirectory)
throw new Error("Multiple directories are not supported");
localDirectory = platform.path().resolve(item);
} else {
localPaths ??= [];
localPaths.push(platform.path().resolve(item));
}
}
if (localPaths?.length && localDirectory)
throw new Error("File paths must be all files or a single directory");
return [localPaths, localDirectory];
}
async function convertInputFiles(platform, files, context) {
const items = Array.isArray(files) ? files.slice() : [files];
if (items.some((item) => typeof item === "string")) {
if (!items.every((item) => typeof item === "string"))
throw new Error("File paths cannot be mixed with buffers");
const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(platform, items);
if (context._connection.isRemote()) {
const files2 = localDirectory ? (await platform.fs().promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter((f) => f.isFile()).map((f) => platform.path().join(f.parentPath, f.name)) : localPaths;
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
rootDirName: localDirectory ? platform.path().basename(localDirectory) : void 0,
items: await Promise.all(files2.map(async (file) => {
const lastModifiedMs = (await platform.fs().promises.stat(file)).mtimeMs;
return {
name: localDirectory ? platform.path().relative(localDirectory, file) : platform.path().basename(file),
lastModifiedMs
};
}))
}), { internal: true });
for (let i = 0; i < files2.length; i++) {
const writable = import_writableStream.WritableStream.from(writableStreams[i]);
await platform.streamFile(files2[i], writable.stream());
}
return {
directoryStream: rootDir,
streams: localDirectory ? void 0 : writableStreams
};
}
return {
localPaths,
localDirectory
};
}
const payloads = items;
if (filePayloadExceedsSizeLimit(payloads))
throw new Error("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
return { payloads };
}
function determineScreenshotType(options) {
if (options.path) {
const mimeType = (0, import_mimeType.getMimeTypeForPath)(options.path);
if (mimeType === "image/png")
return "png";
else if (mimeType === "image/jpeg")
return "jpeg";
throw new Error(`path: unsupported mime type "${mimeType}"`);
}
return options.type;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ElementHandle,
convertInputFiles,
convertSelectOptionValues,
determineScreenshotType
});
@@ -0,0 +1,77 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var errors_exports = {};
__export(errors_exports, {
TargetClosedError: () => TargetClosedError,
TimeoutError: () => TimeoutError,
isTargetClosedError: () => isTargetClosedError,
parseError: () => parseError,
serializeError: () => serializeError
});
module.exports = __toCommonJS(errors_exports);
var import_serializers = require("../protocol/serializers");
var import_rtti = require("../utils/isomorphic/rtti");
class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
}
class TargetClosedError extends Error {
constructor(cause) {
super(cause || "Target page, context or browser has been closed");
}
}
function isTargetClosedError(error) {
return error instanceof TargetClosedError;
}
function serializeError(e) {
if ((0, import_rtti.isError)(e))
return { error: { message: e.message, stack: e.stack, name: e.name } };
return { value: (0, import_serializers.serializeValue)(e, (value) => ({ fallThrough: value })) };
}
function parseError(error) {
if (!error.error) {
if (error.value === void 0)
throw new Error("Serialized error must have either an error or a value");
return (0, import_serializers.parseSerializedValue)(error.value, void 0);
}
if (error.error.name === "TimeoutError") {
const e2 = new TimeoutError(error.error.message);
e2.stack = error.error.stack || "";
return e2;
}
if (error.error.name === "TargetClosedError") {
const e2 = new TargetClosedError(error.error.message);
e2.stack = error.error.stack || "";
return e2;
}
const e = new Error(error.error.message);
e.stack = error.error.stack || "";
e.name = error.error.name;
return e;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TargetClosedError,
TimeoutError,
isTargetClosedError,
parseError,
serializeError
});
@@ -0,0 +1,314 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var eventEmitter_exports = {};
__export(eventEmitter_exports, {
EventEmitter: () => EventEmitter
});
module.exports = __toCommonJS(eventEmitter_exports);
class EventEmitter {
constructor(platform) {
this._events = void 0;
this._eventsCount = 0;
this._maxListeners = void 0;
this._pendingHandlers = /* @__PURE__ */ new Map();
this._platform = platform;
if (this._events === void 0 || this._events === Object.getPrototypeOf(this)._events) {
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || void 0;
this.on = this.addListener;
this.off = this.removeListener;
}
setMaxListeners(n) {
if (typeof n !== "number" || n < 0 || Number.isNaN(n))
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + ".");
this._maxListeners = n;
return this;
}
getMaxListeners() {
return this._maxListeners === void 0 ? this._platform.defaultMaxListeners() : this._maxListeners;
}
emit(type, ...args) {
const events = this._events;
if (events === void 0)
return false;
const handler = events?.[type];
if (handler === void 0)
return false;
if (typeof handler === "function") {
this._callHandler(type, handler, args);
} else {
const len = handler.length;
const listeners = handler.slice();
for (let i = 0; i < len; ++i)
this._callHandler(type, listeners[i], args);
}
return true;
}
_callHandler(type, handler, args) {
const promise = Reflect.apply(handler, this, args);
if (!(promise instanceof Promise))
return;
let set = this._pendingHandlers.get(type);
if (!set) {
set = /* @__PURE__ */ new Set();
this._pendingHandlers.set(type, set);
}
set.add(promise);
promise.catch((e) => {
if (this._rejectionHandler)
this._rejectionHandler(e);
else
throw e;
}).finally(() => set.delete(promise));
}
addListener(type, listener) {
return this._addListener(type, listener, false);
}
on(type, listener) {
return this._addListener(type, listener, false);
}
_addListener(type, listener, prepend) {
checkListener(listener);
let events = this._events;
let existing;
if (events === void 0) {
events = this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
} else {
if (events.newListener !== void 0) {
this.emit("newListener", type, unwrapListener(listener));
events = this._events;
}
existing = events[type];
}
if (existing === void 0) {
existing = events[type] = listener;
++this._eventsCount;
} else {
if (typeof existing === "function") {
existing = events[type] = prepend ? [listener, existing] : [existing, listener];
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
const m = this.getMaxListeners();
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
const w = new Error("Possible EventEmitter memory leak detected. " + existing.length + " " + String(type) + " listeners added. Use emitter.setMaxListeners() to increase limit");
w.name = "MaxListenersExceededWarning";
w.emitter = this;
w.type = type;
w.count = existing.length;
if (!this._platform.isUnderTest()) {
console.warn(w);
}
}
}
return this;
}
prependListener(type, listener) {
return this._addListener(type, listener, true);
}
once(type, listener) {
checkListener(listener);
this.on(type, new OnceWrapper(this, type, listener).wrapperFunction);
return this;
}
prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, new OnceWrapper(this, type, listener).wrapperFunction);
return this;
}
removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === void 0)
return this;
const list = events[type];
if (list === void 0)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) {
this._events = /* @__PURE__ */ Object.create(null);
} else {
delete events[type];
if (events.removeListener)
this.emit("removeListener", type, list.listener ?? listener);
}
} else if (typeof list !== "function") {
let position = -1;
let originalListener;
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || wrappedListener(list[i]) === listener) {
originalListener = wrappedListener(list[i]);
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else
list.splice(position, 1);
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== void 0)
this.emit("removeListener", type, originalListener || listener);
}
return this;
}
off(type, listener) {
return this.removeListener(type, listener);
}
removeAllListeners(type, options) {
this._removeAllListeners(type);
if (!options)
return this;
if (options.behavior === "wait") {
const errors = [];
this._rejectionHandler = (error) => errors.push(error);
return this._waitFor(type).then(() => {
if (errors.length)
throw errors[0];
});
}
if (options.behavior === "ignoreErrors")
this._rejectionHandler = () => {
};
return Promise.resolve();
}
_removeAllListeners(type) {
const events = this._events;
if (!events)
return;
if (!events.removeListener) {
if (type === void 0) {
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== void 0) {
if (--this._eventsCount === 0)
this._events = /* @__PURE__ */ Object.create(null);
else
delete events[type];
}
return;
}
if (type === void 0) {
const keys = Object.keys(events);
let key;
for (let i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === "removeListener")
continue;
this._removeAllListeners(key);
}
this._removeAllListeners("removeListener");
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
return;
}
const listeners = events[type];
if (typeof listeners === "function") {
this.removeListener(type, listeners);
} else if (listeners !== void 0) {
for (let i = listeners.length - 1; i >= 0; i--)
this.removeListener(type, listeners[i]);
}
}
listeners(type) {
return this._listeners(this, type, true);
}
rawListeners(type) {
return this._listeners(this, type, false);
}
listenerCount(type) {
const events = this._events;
if (events !== void 0) {
const listener = events[type];
if (typeof listener === "function")
return 1;
if (listener !== void 0)
return listener.length;
}
return 0;
}
eventNames() {
return this._eventsCount > 0 && this._events ? Reflect.ownKeys(this._events) : [];
}
async _waitFor(type) {
let promises = [];
if (type) {
promises = [...this._pendingHandlers.get(type) || []];
} else {
promises = [];
for (const [, pending] of this._pendingHandlers)
promises.push(...pending);
}
await Promise.all(promises);
}
_listeners(target, type, unwrap) {
const events = target._events;
if (events === void 0)
return [];
const listener = events[type];
if (listener === void 0)
return [];
if (typeof listener === "function")
return unwrap ? [unwrapListener(listener)] : [listener];
return unwrap ? unwrapListeners(listener) : listener.slice();
}
}
function checkListener(listener) {
if (typeof listener !== "function")
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
class OnceWrapper {
constructor(eventEmitter, eventType, listener) {
this._fired = false;
this._eventEmitter = eventEmitter;
this._eventType = eventType;
this._listener = listener;
this.wrapperFunction = this._handle.bind(this);
this.wrapperFunction.listener = listener;
}
_handle(...args) {
if (this._fired)
return;
this._fired = true;
this._eventEmitter.removeListener(this._eventType, this.wrapperFunction);
return this._listener.apply(this._eventEmitter, args);
}
}
function unwrapListener(l) {
return wrappedListener(l) ?? l;
}
function unwrapListeners(arr) {
return arr.map((l) => wrappedListener(l) ?? l);
}
function wrappedListener(l) {
return l.listener;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
EventEmitter
});
@@ -0,0 +1,103 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var events_exports = {};
__export(events_exports, {
Events: () => Events
});
module.exports = __toCommonJS(events_exports);
const Events = {
AndroidDevice: {
WebView: "webview",
Close: "close"
},
AndroidSocket: {
Data: "data",
Close: "close"
},
AndroidWebView: {
Close: "close"
},
Browser: {
Disconnected: "disconnected"
},
Debugger: {
PausedStateChanged: "pausedstatechanged"
},
BrowserContext: {
Console: "console",
Close: "close",
Dialog: "dialog",
Page: "page",
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
WebError: "weberror",
BackgroundPage: "backgroundpage",
// Deprecated in v1.56, never emitted anymore.
ServiceWorker: "serviceworker",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished"
},
BrowserServer: {
Close: "close"
},
Page: {
Close: "close",
Crash: "crash",
Console: "console",
Dialog: "dialog",
Download: "download",
FileChooser: "filechooser",
DOMContentLoaded: "domcontentloaded",
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: "pageerror",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished",
FrameAttached: "frameattached",
FrameDetached: "framedetached",
FrameNavigated: "framenavigated",
Load: "load",
Popup: "popup",
WebSocket: "websocket",
Worker: "worker"
},
WebSocket: {
Close: "close",
Error: "socketerror",
FrameReceived: "framereceived",
FrameSent: "framesent"
},
Worker: {
Close: "close",
Console: "console"
},
ElectronApplication: {
Close: "close",
Console: "console",
Window: "window"
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Events
});
@@ -0,0 +1,367 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var fetch_exports = {};
__export(fetch_exports, {
APIRequest: () => APIRequest,
APIRequestContext: () => APIRequestContext,
APIResponse: () => APIResponse
});
module.exports = __toCommonJS(fetch_exports);
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_network = require("./network");
var import_tracing = require("./tracing");
var import_assert = require("../utils/isomorphic/assert");
var import_fileUtils = require("./fileUtils");
var import_headers = require("../utils/isomorphic/headers");
var import_rtti = require("../utils/isomorphic/rtti");
var import_timeoutSettings = require("./timeoutSettings");
class APIRequest {
constructor(playwright) {
this._contexts = /* @__PURE__ */ new Set();
this._playwright = playwright;
}
async newContext(options = {}) {
options = { ...options };
await this._playwright._instrumentation.runBeforeCreateRequestContext(options);
const storageState = typeof options.storageState === "string" ? JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, "utf8")) : options.storageState;
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
...options,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
storageState,
tracesDir: this._playwright._defaultLaunchOptions?.tracesDir,
// We do not expose tracesDir in the API, so do not allow options to accidentally override it.
clientCertificates: await (0, import_browserContext.toClientCertificatesProtocol)(this._playwright._platform, options.clientCertificates)
})).request);
this._contexts.add(context);
context._request = this;
context._timeoutSettings.setDefaultTimeout(options.timeout ?? this._playwright._defaultContextTimeout);
context._tracing._tracesDir = this._playwright._defaultLaunchOptions?.tracesDir;
await context._instrumentation.runAfterCreateRequestContext(context);
return context;
}
}
class APIRequestContext extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._tracing = import_tracing.Tracing.from(initializer.tracing);
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose(options = {}) {
this._closeReason = options.reason;
await this._instrumentation.runBeforeCloseRequestContext(this);
try {
await this._channel.dispose(options);
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
this._tracing._resetStackCounter();
this._request?._contexts.delete(this);
}
async delete(url, options) {
return await this.fetch(url, {
...options,
method: "DELETE"
});
}
async head(url, options) {
return await this.fetch(url, {
...options,
method: "HEAD"
});
}
async get(url, options) {
return await this.fetch(url, {
...options,
method: "GET"
});
}
async patch(url, options) {
return await this.fetch(url, {
...options,
method: "PATCH"
});
}
async post(url, options) {
return await this.fetch(url, {
...options,
method: "POST"
});
}
async put(url, options) {
return await this.fetch(url, {
...options,
method: "PUT"
});
}
async fetch(urlOrRequest, options = {}) {
const url = (0, import_rtti.isString)(urlOrRequest) ? urlOrRequest : void 0;
const request = (0, import_rtti.isString)(urlOrRequest) ? void 0 : urlOrRequest;
return await this._innerFetch({ url, request, ...options });
}
async _innerFetch(options = {}) {
return await this._wrapApiCall(async () => {
if (this._closeReason)
throw new import_errors.TargetClosedError(this._closeReason);
(0, import_assert.assert)(options.request || typeof options.url === "string", "First argument must be either URL string or Request");
(0, import_assert.assert)((options.data === void 0 ? 0 : 1) + (options.form === void 0 ? 0 : 1) + (options.multipart === void 0 ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
(0, import_assert.assert)(options.maxRedirects === void 0 || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
(0, import_assert.assert)(options.maxRetries === void 0 || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
const url = options.url !== void 0 ? options.url : options.request.url();
const method = options.method || options.request?.method();
let encodedParams = void 0;
if (typeof options.params === "string")
encodedParams = options.params;
else if (options.params instanceof URLSearchParams)
encodedParams = options.params.toString();
const headersObj = options.headers || options.request?.headers();
const headers = headersObj ? (0, import_headers.headersObjectToArray)(headersObj) : void 0;
let jsonData;
let formData;
let multipartData;
let postDataBuffer;
if (options.data !== void 0) {
if ((0, import_rtti.isString)(options.data)) {
if (isJsonContentType(headers))
jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);
else
postDataBuffer = Buffer.from(options.data, "utf8");
} else if (Buffer.isBuffer(options.data)) {
postDataBuffer = options.data;
} else if (typeof options.data === "object" || typeof options.data === "number" || typeof options.data === "boolean") {
jsonData = JSON.stringify(options.data);
} else {
throw new Error(`Unexpected 'data' type`);
}
} else if (options.form) {
if (globalThis.FormData && options.form instanceof FormData) {
formData = [];
for (const [name, value] of options.form.entries()) {
if (typeof value !== "string")
throw new Error(`Expected string for options.form["${name}"], found File. Please use options.multipart instead.`);
formData.push({ name, value });
}
} else {
formData = objectToArray(options.form);
}
} else if (options.multipart) {
multipartData = [];
if (globalThis.FormData && options.multipart instanceof FormData) {
const form = options.multipart;
for (const [name, value] of form.entries()) {
if ((0, import_rtti.isString)(value)) {
multipartData.push({ name, value });
} else {
const file = {
name: value.name,
mimeType: value.type,
buffer: Buffer.from(await value.arrayBuffer())
};
multipartData.push({ name, file });
}
}
} else {
for (const [name, value] of Object.entries(options.multipart))
multipartData.push(await toFormField(this._platform, name, value));
}
}
if (postDataBuffer === void 0 && jsonData === void 0 && formData === void 0 && multipartData === void 0)
postDataBuffer = options.request?.postDataBuffer() || void 0;
const fixtures = {
__testHookLookup: options.__testHookLookup
};
const result = await this._channel.fetch({
url,
params: typeof options.params === "object" ? objectToArray(options.params) : void 0,
encodedParams,
method,
headers,
postData: postDataBuffer,
jsonData,
formData,
multipartData,
timeout: this._timeoutSettings.timeout(options),
failOnStatusCode: options.failOnStatusCode,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
maxRedirects: options.maxRedirects,
maxRetries: options.maxRetries,
...fixtures
});
return new APIResponse(this, result.response);
});
}
async storageState(options = {}) {
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
}
return state;
}
}
async function toFormField(platform, name, value) {
const typeOfValue = typeof value;
if (isFilePayload(value)) {
const payload = value;
if (!Buffer.isBuffer(payload.buffer))
throw new Error(`Unexpected buffer type of 'data.${name}'`);
return { name, file: filePayloadToJson(payload) };
} else if (typeOfValue === "string" || typeOfValue === "number" || typeOfValue === "boolean") {
return { name, value: String(value) };
} else {
return { name, file: await readStreamToJson(platform, value) };
}
}
function isJsonParsable(value) {
if (typeof value !== "string")
return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError)
return false;
else
throw e;
}
}
class APIResponse {
constructor(context, initializer) {
this._request = context;
this._initializer = initializer;
this._headers = new import_network.RawHeaders(this._initializer.headers);
if (context._platform.inspectCustom)
this[context._platform.inspectCustom] = () => this._inspect();
}
ok() {
return this._initializer.status >= 200 && this._initializer.status <= 299;
}
url() {
return this._initializer.url;
}
status() {
return this._initializer.status;
}
statusText() {
return this._initializer.statusText;
}
headers() {
return this._headers.headers();
}
headersArray() {
return this._headers.headersArray();
}
async body() {
return await this._request._wrapApiCall(async () => {
try {
const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() });
if (result.binary === void 0)
throw new Error("Response has been disposed");
return result.binary;
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
throw new Error("Response has been disposed");
throw e;
}
}, { internal: true });
}
async text() {
const content = await this.body();
return content.toString("utf8");
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
}
_inspect() {
const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`);
return `APIResponse: ${this.status()} ${this.statusText()}
${headers.join("\n")}`;
}
_fetchUid() {
return this._initializer.fetchUid;
}
async _fetchLog() {
const { log } = await this._request._channel.fetchLog({ fetchUid: this._fetchUid() });
return log;
}
}
function filePayloadToJson(payload) {
return {
name: payload.name,
mimeType: payload.mimeType,
buffer: payload.buffer
};
}
async function readStreamToJson(platform, stream) {
const buffer = await new Promise((resolve, reject) => {
const chunks = [];
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => resolve(Buffer.concat(chunks)));
stream.on("error", (err) => reject(err));
});
const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString("utf8") : stream.path;
return {
name: platform.path().basename(streamPath),
buffer
};
}
function isJsonContentType(headers) {
if (!headers)
return false;
for (const { name, value } of headers) {
if (name.toLocaleLowerCase() === "content-type")
return value === "application/json";
}
return false;
}
function objectToArray(map) {
if (!map)
return void 0;
const result = [];
for (const [name, value] of Object.entries(map)) {
if (value !== void 0)
result.push({ name, value: String(value) });
}
return result;
}
function isFilePayload(value) {
return typeof value === "object" && value["name"] && value["mimeType"] && value["buffer"];
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
APIRequest,
APIRequestContext,
APIResponse
});
@@ -0,0 +1,46 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var fileChooser_exports = {};
__export(fileChooser_exports, {
FileChooser: () => FileChooser
});
module.exports = __toCommonJS(fileChooser_exports);
class FileChooser {
constructor(page, elementHandle, isMultiple) {
this._page = page;
this._elementHandle = elementHandle;
this._isMultiple = isMultiple;
}
element() {
return this._elementHandle;
}
isMultiple() {
return this._isMultiple;
}
page() {
return this._page;
}
async setFiles(files, options) {
return await this._elementHandle.setInputFiles(files, options);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FileChooser
});
@@ -0,0 +1,34 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var fileUtils_exports = {};
__export(fileUtils_exports, {
fileUploadSizeLimit: () => fileUploadSizeLimit,
mkdirIfNeeded: () => mkdirIfNeeded
});
module.exports = __toCommonJS(fileUtils_exports);
const fileUploadSizeLimit = 50 * 1024 * 1024;
async function mkdirIfNeeded(platform, filePath) {
await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
fileUploadSizeLimit,
mkdirIfNeeded
});
@@ -0,0 +1,404 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var frame_exports = {};
__export(frame_exports, {
Frame: () => Frame,
verifyLoadState: () => verifyLoadState
});
module.exports = __toCommonJS(frame_exports);
var import_eventEmitter = require("./eventEmitter");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_elementHandle = require("./elementHandle");
var import_events = require("./events");
var import_jsHandle = require("./jsHandle");
var import_locator = require("./locator");
var network = __toESM(require("./network"));
var import_types = require("./types");
var import_waiter = require("./waiter");
var import_assert = require("../utils/isomorphic/assert");
var import_locatorUtils = require("../utils/isomorphic/locatorUtils");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_timeoutSettings = require("./timeoutSettings");
class Frame extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._parentFrame = null;
this._url = "";
this._name = "";
this._detached = false;
this._childFrames = /* @__PURE__ */ new Set();
this._eventEmitter = new import_eventEmitter.EventEmitter(parent._platform);
this._eventEmitter.setMaxListeners(0);
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
if (this._parentFrame)
this._parentFrame._childFrames.add(this);
this._name = initializer.name;
this._url = initializer.url;
this._loadStates = new Set(initializer.loadStates);
this._channel.on("loadstate", (event) => {
if (event.add) {
this._loadStates.add(event.add);
this._eventEmitter.emit("loadstate", event.add);
}
if (event.remove)
this._loadStates.delete(event.remove);
if (!this._parentFrame && event.add === "load" && this._page)
this._page.emit(import_events.Events.Page.Load, this._page);
if (!this._parentFrame && event.add === "domcontentloaded" && this._page)
this._page.emit(import_events.Events.Page.DOMContentLoaded, this._page);
});
this._channel.on("navigated", (event) => {
this._url = event.url;
this._name = event.name;
this._eventEmitter.emit("navigated", event);
if (!event.error && this._page)
this._page.emit(import_events.Events.Page.FrameNavigated, this);
});
}
static from(frame) {
return frame._object;
}
static fromNullable(frame) {
return frame ? Frame.from(frame) : null;
}
page() {
return this._page;
}
_timeout(options) {
const timeoutSettings = this._page?._timeoutSettings || new import_timeoutSettings.TimeoutSettings(this._platform);
return timeoutSettings.timeout(options || {});
}
_navigationTimeout(options) {
const timeoutSettings = this._page?._timeoutSettings || new import_timeoutSettings.TimeoutSettings(this._platform);
return timeoutSettings.navigationTimeout(options || {});
}
async goto(url, options = {}) {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
return network.Response.fromNullable((await this._channel.goto({ url, ...options, waitUntil, timeout: this._navigationTimeout(options) })).response);
}
_setupNavigationWaiter(options) {
const waiter = new import_waiter.Waiter(this._page, "");
if (this._page.isClosed())
waiter.rejectImmediately(this._page._closeErrorWithReason());
waiter.rejectOnEvent(this._page, import_events.Events.Page.Close, () => this._page._closeErrorWithReason());
waiter.rejectOnEvent(this._page, import_events.Events.Page.Crash, new Error("Navigation failed because page crashed!"));
waiter.rejectOnEvent(this._page, import_events.Events.Page.FrameDetached, new Error("Navigating frame was detached!"), (frame) => frame === this);
const timeout = this._page._timeoutSettings.navigationTimeout(options);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded.`);
return waiter;
}
async waitForNavigation(options = {}) {
return await this._page._wrapApiCall(async () => {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
const waiter = this._setupNavigationWaiter(options);
const toUrl = typeof options.url === "string" ? ` to "${options.url}"` : "";
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, "navigated", (event) => {
if (event.error)
return true;
waiter.log(` navigated to "${event.url}"`);
return (0, import_urlMatch.urlMatches)(this._page?.context()._options.baseURL, event.url, options.url);
});
if (navigatedEvent.error) {
const e = new Error(navigatedEvent.error);
e.stack = "";
await waiter.waitForPromise(Promise.reject(e));
}
if (!this._loadStates.has(waitUntil)) {
await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
waiter.log(` "${s}" event fired`);
return s === waitUntil;
});
}
const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request) : null;
const response = request ? await waiter.waitForPromise(request._finalRequest()._internalResponse()) : null;
waiter.dispose();
return response;
}, { title: "Wait for navigation" });
}
async waitForLoadState(state = "load", options = {}) {
state = verifyLoadState("state", state);
return await this._page._wrapApiCall(async () => {
const waiter = this._setupNavigationWaiter(options);
if (this._loadStates.has(state)) {
waiter.log(` not waiting, "${state}" event already fired`);
} else {
await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
waiter.log(` "${s}" event fired`);
return s === state;
});
}
waiter.dispose();
}, { title: `Wait for load state "${state}"` });
}
async waitForURL(url, options = {}) {
if ((0, import_urlMatch.urlMatches)(this._page?.context()._options.baseURL, this.url(), url))
return await this.waitForLoadState(options.waitUntil, options);
await this.waitForNavigation({ url, ...options });
}
async frameElement() {
return import_elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
}
async evaluateHandle(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return import_jsHandle.JSHandle.from(result.handle);
}
async evaluate(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async _evaluateExposeUtilityScript(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $(selector, options) {
const result = await this._channel.querySelector({ selector, ...options });
return import_elementHandle.ElementHandle.fromNullable(result.element);
}
async waitForSelector(selector, options = {}) {
if (options.visibility)
throw new Error("options.visibility is not supported, did you mean options.state?");
if (options.waitFor && options.waitFor !== "visible")
throw new Error("options.waitFor is not supported, did you mean options.state?");
const result = await this._channel.waitForSelector({ selector, ...options, timeout: this._timeout(options) });
return import_elementHandle.ElementHandle.fromNullable(result.element);
}
async dispatchEvent(selector, type, eventInit, options = {}) {
await this._channel.dispatchEvent({ selector, type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit), ...options, timeout: this._timeout(options) });
}
async $eval(selector, pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $$eval(selector, pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $$(selector) {
const result = await this._channel.querySelectorAll({ selector });
return result.elements.map((e) => import_elementHandle.ElementHandle.from(e));
}
async _queryCount(selector, options) {
return (await this._channel.queryCount({ selector, ...options })).value;
}
async content() {
return (await this._channel.content()).value;
}
async setContent(html, options = {}) {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
await this._channel.setContent({ html, ...options, waitUntil, timeout: this._navigationTimeout(options) });
}
name() {
return this._name || "";
}
url() {
return this._url;
}
parentFrame() {
return this._parentFrame;
}
childFrames() {
return Array.from(this._childFrames);
}
isDetached() {
return this._detached;
}
async addScriptTag(options = {}) {
const copy = { ...options };
if (copy.path) {
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
copy.content = (0, import_clientHelper.addSourceUrlToScript)(copy.content, copy.path);
}
return import_elementHandle.ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
}
async addStyleTag(options = {}) {
const copy = { ...options };
if (copy.path) {
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
copy.content += "/*# sourceURL=" + copy.path.replace(/\n/g, "") + "*/";
}
return import_elementHandle.ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element);
}
async click(selector, options = {}) {
return await this._channel.click({ selector, ...options, timeout: this._timeout(options) });
}
async dblclick(selector, options = {}) {
return await this._channel.dblclick({ selector, ...options, timeout: this._timeout(options) });
}
async dragAndDrop(source, target, options = {}) {
return await this._channel.dragAndDrop({ source, target, ...options, timeout: this._timeout(options) });
}
async tap(selector, options = {}) {
return await this._channel.tap({ selector, ...options, timeout: this._timeout(options) });
}
async fill(selector, value, options = {}) {
return await this._channel.fill({ selector, value, ...options, timeout: this._timeout(options) });
}
async _highlight(selector) {
return await this._channel.highlight({ selector });
}
locator(selector, options) {
return new import_locator.Locator(this, selector, options);
}
getByTestId(testId) {
return this.locator((0, import_locatorUtils.getByTestIdSelector)((0, import_locator.testIdAttributeName)(), testId));
}
getByAltText(text, options) {
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new import_locator.FrameLocator(this, selector);
}
async focus(selector, options = {}) {
await this._channel.focus({ selector, ...options, timeout: this._timeout(options) });
}
async textContent(selector, options = {}) {
const value = (await this._channel.textContent({ selector, ...options, timeout: this._timeout(options) })).value;
return value === void 0 ? null : value;
}
async innerText(selector, options = {}) {
return (await this._channel.innerText({ selector, ...options, timeout: this._timeout(options) })).value;
}
async innerHTML(selector, options = {}) {
return (await this._channel.innerHTML({ selector, ...options, timeout: this._timeout(options) })).value;
}
async getAttribute(selector, name, options = {}) {
const value = (await this._channel.getAttribute({ selector, name, ...options, timeout: this._timeout(options) })).value;
return value === void 0 ? null : value;
}
async inputValue(selector, options = {}) {
return (await this._channel.inputValue({ selector, ...options, timeout: this._timeout(options) })).value;
}
async isChecked(selector, options = {}) {
return (await this._channel.isChecked({ selector, ...options, timeout: this._timeout(options) })).value;
}
async isDisabled(selector, options = {}) {
return (await this._channel.isDisabled({ selector, ...options, timeout: this._timeout(options) })).value;
}
async isEditable(selector, options = {}) {
return (await this._channel.isEditable({ selector, ...options, timeout: this._timeout(options) })).value;
}
async isEnabled(selector, options = {}) {
return (await this._channel.isEnabled({ selector, ...options, timeout: this._timeout(options) })).value;
}
async isHidden(selector, options = {}) {
return (await this._channel.isHidden({ selector, ...options })).value;
}
async isVisible(selector, options = {}) {
return (await this._channel.isVisible({ selector, ...options })).value;
}
async hover(selector, options = {}) {
await this._channel.hover({ selector, ...options, timeout: this._timeout(options) });
}
async selectOption(selector, values, options = {}) {
return (await this._channel.selectOption({ selector, ...(0, import_elementHandle.convertSelectOptionValues)(values), ...options, timeout: this._timeout(options) })).values;
}
async setInputFiles(selector, files, options = {}) {
const converted = await (0, import_elementHandle.convertInputFiles)(this._platform, files, this.page().context());
await this._channel.setInputFiles({ selector, ...converted, ...options, timeout: this._timeout(options) });
}
async type(selector, text, options = {}) {
await this._channel.type({ selector, text, ...options, timeout: this._timeout(options) });
}
async press(selector, key, options = {}) {
await this._channel.press({ selector, key, ...options, timeout: this._timeout(options) });
}
async check(selector, options = {}) {
await this._channel.check({ selector, ...options, timeout: this._timeout(options) });
}
async uncheck(selector, options = {}) {
await this._channel.uncheck({ selector, ...options, timeout: this._timeout(options) });
}
async setChecked(selector, checked, options) {
if (checked)
await this.check(selector, options);
else
await this.uncheck(selector, options);
}
async waitForTimeout(timeout) {
await this._channel.waitForTimeout({ waitTimeout: timeout });
}
async waitForFunction(pageFunction, arg, options = {}) {
if (typeof options.polling === "string")
(0, import_assert.assert)(options.polling === "raf", "Unknown polling option: " + options.polling);
const result = await this._channel.waitForFunction({
...options,
pollingInterval: options.polling === "raf" ? void 0 : options.polling,
expression: String(pageFunction),
isFunction: typeof pageFunction === "function",
arg: (0, import_jsHandle.serializeArgument)(arg),
timeout: this._timeout(options)
});
return import_jsHandle.JSHandle.from(result.handle);
}
async title() {
return (await this._channel.title()).value;
}
async _expect(expression, options) {
const params = { expression, ...options, isNot: !!options.isNot };
params.expectedValue = (0, import_jsHandle.serializeArgument)(options.expectedValue);
const result = await this._channel.expect(params);
if (result.received !== void 0)
result.received = (0, import_jsHandle.parseResult)(result.received);
return result;
}
}
function verifyLoadState(name, waitUntil) {
if (waitUntil === "networkidle0")
waitUntil = "networkidle";
if (!import_types.kLifecycleEvents.has(waitUntil))
throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
return waitUntil;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Frame,
verifyLoadState
});
@@ -0,0 +1,99 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var harRouter_exports = {};
__export(harRouter_exports, {
HarRouter: () => HarRouter
});
module.exports = __toCommonJS(harRouter_exports);
class HarRouter {
static async create(localUtils, file, notFoundAction, options) {
const { harId, error } = await localUtils.harOpen({ file });
if (error)
throw new Error(error);
return new HarRouter(localUtils, harId, notFoundAction, options);
}
constructor(localUtils, harId, notFoundAction, options) {
this._localUtils = localUtils;
this._harId = harId;
this._options = options;
this._notFoundAction = notFoundAction;
}
async _handle(route) {
const request = route.request();
const response = await this._localUtils.harLookup({
harId: this._harId,
url: request.url(),
method: request.method(),
headers: await request.headersArray(),
postData: request.postDataBuffer() || void 0,
isNavigationRequest: request.isNavigationRequest()
});
if (response.action === "redirect") {
route._platform.log("api", `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
await route._redirectNavigationRequest(response.redirectURL);
return;
}
if (response.action === "fulfill") {
if (response.status === -1)
return;
const transformedHeaders = response.headers.reduce((headersMap, { name, value }) => {
if (name.toLowerCase() !== "set-cookie") {
headersMap[name] = value;
} else {
if (!headersMap["set-cookie"])
headersMap["set-cookie"] = value;
else
headersMap["set-cookie"] += `
${value}`;
}
return headersMap;
}, {});
await route.fulfill({
status: response.status,
headers: transformedHeaders,
body: response.body
});
return;
}
if (response.action === "error")
route._platform.log("api", "HAR: " + response.message);
if (this._notFoundAction === "abort") {
await route.abort();
return;
}
await route.fallback();
}
async addContextRoute(context) {
await context.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
}
async addPageRoute(page) {
await page.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
dispose() {
this._localUtils.harClose({ harId: this._harId }).catch(() => {
});
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
HarRouter
});
@@ -0,0 +1,84 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var input_exports = {};
__export(input_exports, {
Keyboard: () => Keyboard,
Mouse: () => Mouse,
Touchscreen: () => Touchscreen
});
module.exports = __toCommonJS(input_exports);
class Keyboard {
constructor(page) {
this._page = page;
}
async down(key) {
await this._page._channel.keyboardDown({ key });
}
async up(key) {
await this._page._channel.keyboardUp({ key });
}
async insertText(text) {
await this._page._channel.keyboardInsertText({ text });
}
async type(text, options = {}) {
await this._page._channel.keyboardType({ text, ...options });
}
async press(key, options = {}) {
await this._page._channel.keyboardPress({ key, ...options });
}
}
class Mouse {
constructor(page) {
this._page = page;
}
async move(x, y, options = {}) {
await this._page._channel.mouseMove({ x, y, ...options });
}
async down(options = {}) {
await this._page._channel.mouseDown({ ...options });
}
async up(options = {}) {
await this._page._channel.mouseUp(options);
}
async click(x, y, options = {}) {
await this._page._channel.mouseClick({ x, y, ...options });
}
async dblclick(x, y, options = {}) {
await this._page._wrapApiCall(async () => {
await this.click(x, y, { ...options, clickCount: 2 });
}, { title: "Double click" });
}
async wheel(deltaX, deltaY) {
await this._page._channel.mouseWheel({ deltaX, deltaY });
}
}
class Touchscreen {
constructor(page) {
this._page = page;
}
async tap(x, y) {
await this._page._channel.touchscreenTap({ x, y });
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Keyboard,
Mouse,
Touchscreen
});
@@ -0,0 +1,105 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var jsHandle_exports = {};
__export(jsHandle_exports, {
JSHandle: () => JSHandle,
assertMaxArguments: () => assertMaxArguments,
parseResult: () => parseResult,
serializeArgument: () => serializeArgument
});
module.exports = __toCommonJS(jsHandle_exports);
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_serializers = require("../protocol/serializers");
class JSHandle extends import_channelOwner.ChannelOwner {
static from(handle) {
return handle._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._preview = this._initializer.preview;
this._channel.on("previewUpdated", ({ preview }) => this._preview = preview);
}
async evaluate(pageFunction, arg) {
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: serializeArgument(arg) });
return parseResult(result.value);
}
async evaluateHandle(pageFunction, arg) {
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: serializeArgument(arg) });
return JSHandle.from(result.handle);
}
async getProperty(propertyName) {
const result = await this._channel.getProperty({ name: propertyName });
return JSHandle.from(result.handle);
}
async getProperties() {
const map = /* @__PURE__ */ new Map();
for (const { name, value } of (await this._channel.getPropertyList()).properties)
map.set(name, JSHandle.from(value));
return map;
}
async jsonValue() {
return parseResult((await this._channel.jsonValue()).value);
}
asElement() {
return null;
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
try {
await this._channel.dispose();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
toString() {
return this._preview;
}
}
function serializeArgument(arg) {
const handles = [];
const pushHandle = (channel) => {
handles.push(channel);
return handles.length - 1;
};
const value = (0, import_serializers.serializeValue)(arg, (value2) => {
if (value2 instanceof JSHandle)
return { h: pushHandle(value2._channel) };
return { fallThrough: value2 };
});
return { value, handles };
}
function parseResult(value) {
return (0, import_serializers.parseSerializedValue)(value, void 0);
}
function assertMaxArguments(count, max) {
if (count > max)
throw new Error("Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.");
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
JSHandle,
assertMaxArguments,
parseResult,
serializeArgument
});
@@ -0,0 +1,39 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var jsonPipe_exports = {};
__export(jsonPipe_exports, {
JsonPipe: () => JsonPipe
});
module.exports = __toCommonJS(jsonPipe_exports);
var import_channelOwner = require("./channelOwner");
class JsonPipe extends import_channelOwner.ChannelOwner {
static from(jsonPipe) {
return jsonPipe._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
channel() {
return this._channel;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
JsonPipe
});
@@ -0,0 +1,60 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var localUtils_exports = {};
__export(localUtils_exports, {
LocalUtils: () => LocalUtils
});
module.exports = __toCommonJS(localUtils_exports);
var import_channelOwner = require("./channelOwner");
class LocalUtils extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this.devices = {};
for (const { name, descriptor } of initializer.deviceDescriptors)
this.devices[name] = descriptor;
}
async zip(params) {
return await this._channel.zip(params);
}
async harOpen(params) {
return await this._channel.harOpen(params);
}
async harLookup(params) {
return await this._channel.harLookup(params);
}
async harClose(params) {
return await this._channel.harClose(params);
}
async harUnzip(params) {
return await this._channel.harUnzip(params);
}
async tracingStarted(params) {
return await this._channel.tracingStarted(params);
}
async traceDiscarded(params) {
return await this._channel.traceDiscarded(params);
}
async addStackToTracingNoReply(params) {
return await this._channel.addStackToTracingNoReply(params);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LocalUtils
});
@@ -0,0 +1,367 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var locator_exports = {};
__export(locator_exports, {
FrameLocator: () => FrameLocator,
Locator: () => Locator,
setTestIdAttribute: () => setTestIdAttribute,
testIdAttributeName: () => testIdAttributeName
});
module.exports = __toCommonJS(locator_exports);
var import_elementHandle = require("./elementHandle");
var import_locatorGenerators = require("../utils/isomorphic/locatorGenerators");
var import_locatorUtils = require("../utils/isomorphic/locatorUtils");
var import_stringUtils = require("../utils/isomorphic/stringUtils");
var import_rtti = require("../utils/isomorphic/rtti");
var import_time = require("../utils/isomorphic/time");
class Locator {
constructor(frame, selector, options) {
this._frame = frame;
this._selector = selector;
if (options?.hasText)
this._selector += ` >> internal:has-text=${(0, import_stringUtils.escapeForTextSelector)(options.hasText, false)}`;
if (options?.hasNotText)
this._selector += ` >> internal:has-not-text=${(0, import_stringUtils.escapeForTextSelector)(options.hasNotText, false)}`;
if (options?.has) {
const locator = options.has;
if (locator._frame !== frame)
throw new Error(`Inner "has" locator must belong to the same frame.`);
this._selector += ` >> internal:has=` + JSON.stringify(locator._selector);
}
if (options?.hasNot) {
const locator = options.hasNot;
if (locator._frame !== frame)
throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
}
if (options?.visible !== void 0)
this._selector += ` >> visible=${options.visible ? "true" : "false"}`;
if (this._frame._platform.inspectCustom)
this[this._frame._platform.inspectCustom] = () => this._inspect();
}
async _withElement(task, options) {
const timeout = this._frame._timeout({ timeout: options.timeout });
const deadline = timeout ? (0, import_time.monotonicTime)() + timeout : 0;
return await this._frame._wrapApiCall(async () => {
const result = await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, state: "attached", timeout });
const handle = import_elementHandle.ElementHandle.fromNullable(result.element);
if (!handle)
throw new Error(`Could not resolve ${this._selector} to DOM Element`);
try {
return await task(handle, deadline ? deadline - (0, import_time.monotonicTime)() : 0);
} finally {
await handle.dispose();
}
}, { title: options.title, internal: options.internal });
}
_equals(locator) {
return this._frame === locator._frame && this._selector === locator._selector;
}
page() {
return this._frame.page();
}
async boundingBox(options) {
return await this._withElement((h) => h.boundingBox(), { title: "Bounding box", timeout: options?.timeout });
}
async check(options = {}) {
return await this._frame.check(this._selector, { strict: true, ...options });
}
async click(options = {}) {
return await this._frame.click(this._selector, { strict: true, ...options });
}
async dblclick(options = {}) {
await this._frame.dblclick(this._selector, { strict: true, ...options });
}
async dispatchEvent(type, eventInit = {}, options) {
return await this._frame.dispatchEvent(this._selector, type, eventInit, { strict: true, ...options });
}
async dragTo(target, options = {}) {
return await this._frame.dragAndDrop(this._selector, target._selector, {
strict: true,
...options
});
}
async evaluate(pageFunction, arg, options) {
return await this._withElement((h) => h.evaluate(pageFunction, arg), { title: "Evaluate", timeout: options?.timeout });
}
async evaluateAll(pageFunction, arg) {
return await this._frame.$$eval(this._selector, pageFunction, arg);
}
async evaluateHandle(pageFunction, arg, options) {
return await this._withElement((h) => h.evaluateHandle(pageFunction, arg), { title: "Evaluate", timeout: options?.timeout });
}
async fill(value, options = {}) {
return await this._frame.fill(this._selector, value, { strict: true, ...options });
}
async clear(options = {}) {
await this._frame._wrapApiCall(() => this.fill("", options), { title: "Clear" });
}
async _highlight() {
return await this._frame._highlight(this._selector);
}
async highlight() {
return await this._frame._highlight(this._selector);
}
locator(selectorOrLocator, options) {
if ((0, import_rtti.isString)(selectorOrLocator))
return new Locator(this._frame, this._selector + " >> " + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + " >> internal:chain=" + JSON.stringify(selectorOrLocator._selector), options);
}
getByTestId(testId) {
return this.locator((0, import_locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
}
getByAltText(text, options) {
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new FrameLocator(this._frame, this._selector + " >> " + selector);
}
filter(options) {
return new Locator(this._frame, this._selector, options);
}
async elementHandle(options) {
return await this._frame.waitForSelector(this._selector, { strict: true, state: "attached", ...options });
}
async elementHandles() {
return await this._frame.$$(this._selector);
}
contentFrame() {
return new FrameLocator(this._frame, this._selector);
}
describe(description) {
return new Locator(this._frame, this._selector + " >> internal:describe=" + JSON.stringify(description));
}
description() {
return (0, import_locatorGenerators.locatorCustomDescription)(this._selector) || null;
}
first() {
return new Locator(this._frame, this._selector + " >> nth=0");
}
last() {
return new Locator(this._frame, this._selector + ` >> nth=-1`);
}
nth(index) {
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
}
and(locator) {
if (locator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
}
or(locator) {
if (locator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:or=` + JSON.stringify(locator._selector));
}
async focus(options) {
return await this._frame.focus(this._selector, { strict: true, ...options });
}
async blur(options) {
await this._frame._channel.blur({ selector: this._selector, strict: true, ...options, timeout: this._frame._timeout(options) });
}
// options are only here for testing
async count(_options) {
return await this._frame._queryCount(this._selector, _options);
}
async normalize() {
const { resolvedSelector } = await this._frame._channel.resolveSelector({ selector: this._selector });
return new Locator(this._frame, resolvedSelector);
}
async getAttribute(name, options) {
return await this._frame.getAttribute(this._selector, name, { strict: true, ...options });
}
async hover(options = {}) {
return await this._frame.hover(this._selector, { strict: true, ...options });
}
async innerHTML(options) {
return await this._frame.innerHTML(this._selector, { strict: true, ...options });
}
async innerText(options) {
return await this._frame.innerText(this._selector, { strict: true, ...options });
}
async inputValue(options) {
return await this._frame.inputValue(this._selector, { strict: true, ...options });
}
async isChecked(options) {
return await this._frame.isChecked(this._selector, { strict: true, ...options });
}
async isDisabled(options) {
return await this._frame.isDisabled(this._selector, { strict: true, ...options });
}
async isEditable(options) {
return await this._frame.isEditable(this._selector, { strict: true, ...options });
}
async isEnabled(options) {
return await this._frame.isEnabled(this._selector, { strict: true, ...options });
}
async isHidden(options) {
return await this._frame.isHidden(this._selector, { strict: true, ...options });
}
async isVisible(options) {
return await this._frame.isVisible(this._selector, { strict: true, ...options });
}
async press(key, options = {}) {
return await this._frame.press(this._selector, key, { strict: true, ...options });
}
async screenshot(options = {}) {
const mask = options.mask;
return await this._withElement((h, timeout) => h.screenshot({ ...options, mask, timeout }), { title: "Screenshot", timeout: options.timeout });
}
async ariaSnapshot(options = {}) {
const result = await this._frame._channel.ariaSnapshot({ timeout: this._frame._timeout(options), mode: options.mode, selector: this._selector, depth: options.depth });
return result.snapshot;
}
async scrollIntoViewIfNeeded(options = {}) {
return await this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({ ...options, timeout }), { title: "Scroll into view", timeout: options.timeout });
}
async selectOption(values, options = {}) {
return await this._frame.selectOption(this._selector, values, { strict: true, ...options });
}
async selectText(options = {}) {
return await this._withElement((h, timeout) => h.selectText({ ...options, timeout }), { title: "Select text", timeout: options.timeout });
}
async setChecked(checked, options) {
if (checked)
await this.check(options);
else
await this.uncheck(options);
}
async setInputFiles(files, options = {}) {
return await this._frame.setInputFiles(this._selector, files, { strict: true, ...options });
}
async tap(options = {}) {
return await this._frame.tap(this._selector, { strict: true, ...options });
}
async textContent(options) {
return await this._frame.textContent(this._selector, { strict: true, ...options });
}
async type(text, options = {}) {
return await this._frame.type(this._selector, text, { strict: true, ...options });
}
async pressSequentially(text, options = {}) {
return await this.type(text, options);
}
async uncheck(options = {}) {
return await this._frame.uncheck(this._selector, { strict: true, ...options });
}
async all() {
return new Array(await this.count()).fill(0).map((e, i) => this.nth(i));
}
async allInnerTexts() {
return await this._frame.$$eval(this._selector, (ee) => ee.map((e) => e.innerText));
}
async allTextContents() {
return await this._frame.$$eval(this._selector, (ee) => ee.map((e) => e.textContent || ""));
}
async waitFor(options) {
await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options, timeout: this._frame._timeout(options) });
}
async _expect(expression, options) {
return this._frame._expect(expression, {
...options,
selector: this._selector
});
}
_inspect() {
return this.toString();
}
toString() {
return (0, import_locatorGenerators.asLocatorDescription)("javascript", this._selector);
}
}
class FrameLocator {
constructor(frame, selector) {
this._frame = frame;
this._frameSelector = selector;
}
locator(selectorOrLocator, options) {
if ((0, import_rtti.isString)(selectorOrLocator))
return new Locator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selectorOrLocator._selector, options);
}
getByTestId(testId) {
return this.locator((0, import_locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
}
getByAltText(text, options) {
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
owner() {
return new Locator(this._frame, this._frameSelector);
}
frameLocator(selector) {
return new FrameLocator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selector);
}
first() {
return new FrameLocator(this._frame, this._frameSelector + " >> nth=0");
}
last() {
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`);
}
nth(index) {
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`);
}
}
let _testIdAttributeName = "data-testid";
function testIdAttributeName() {
return _testIdAttributeName;
}
function setTestIdAttribute(attributeName) {
_testIdAttributeName = attributeName;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FrameLocator,
Locator,
setTestIdAttribute,
testIdAttributeName
});
@@ -0,0 +1,750 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var network_exports = {};
__export(network_exports, {
RawHeaders: () => RawHeaders,
Request: () => Request,
Response: () => Response,
Route: () => Route,
RouteHandler: () => RouteHandler,
WebSocket: () => WebSocket,
WebSocketRoute: () => WebSocketRoute,
WebSocketRouteHandler: () => WebSocketRouteHandler,
validateHeaders: () => validateHeaders
});
module.exports = __toCommonJS(network_exports);
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fetch = require("./fetch");
var import_frame = require("./frame");
var import_waiter = require("./waiter");
var import_worker = require("./worker");
var import_assert = require("../utils/isomorphic/assert");
var import_headers = require("../utils/isomorphic/headers");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_manualPromise = require("../utils/isomorphic/manualPromise");
var import_multimap = require("../utils/isomorphic/multimap");
var import_rtti = require("../utils/isomorphic/rtti");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
var import_mimeType = require("../utils/isomorphic/mimeType");
class Request extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._redirectedFrom = null;
this._redirectedTo = null;
this._failureText = null;
this._response = null;
this._fallbackOverrides = {};
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
if (this._redirectedFrom)
this._redirectedFrom._redirectedTo = this;
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._timing = {
startTime: 0,
domainLookupStart: -1,
domainLookupEnd: -1,
connectStart: -1,
secureConnectionStart: -1,
connectEnd: -1,
requestStart: -1,
responseStart: -1,
responseEnd: -1
};
}
static from(request) {
return request._object;
}
static fromNullable(request) {
return request ? Request.from(request) : null;
}
url() {
return this._fallbackOverrides.url || this._initializer.url;
}
resourceType() {
return this._initializer.resourceType;
}
method() {
return this._fallbackOverrides.method || this._initializer.method;
}
postData() {
return (this._fallbackOverrides.postDataBuffer || this._initializer.postData)?.toString("utf-8") || null;
}
postDataBuffer() {
return this._fallbackOverrides.postDataBuffer || this._initializer.postData || null;
}
postDataJSON() {
const postData = this.postData();
if (!postData)
return null;
const contentType = this.headers()["content-type"];
if (contentType?.includes("application/x-www-form-urlencoded")) {
const entries = {};
const parsed = new URLSearchParams(postData);
for (const [k, v] of parsed.entries())
entries[k] = v;
return entries;
}
try {
return JSON.parse(postData);
} catch (e) {
throw new Error("POST data is not a valid JSON object: " + postData);
}
}
/**
* @deprecated
*/
headers() {
if (this._fallbackOverrides.headers)
return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
return this._provisionalHeaders.headers();
}
async _actualHeaders() {
if (this._fallbackOverrides.headers)
return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers);
if (!this._actualHeadersPromise) {
this._actualHeadersPromise = this._wrapApiCall(async () => {
return new RawHeaders((await this._channel.rawRequestHeaders()).headers);
}, { internal: true });
}
return await this._actualHeadersPromise;
}
async allHeaders() {
return (await this._actualHeaders()).headers();
}
async headersArray() {
return (await this._actualHeaders()).headersArray();
}
async headerValue(name) {
return (await this._actualHeaders()).get(name);
}
async response() {
return Response.fromNullable((await this._channel.response()).response);
}
async _internalResponse() {
return Response.fromNullable((await this._channel.response()).response);
}
existingResponse() {
return this._response;
}
frame() {
if (!this._initializer.frame) {
(0, import_assert.assert)(this.serviceWorker());
throw new Error("Service Worker requests do not have an associated frame.");
}
const frame = import_frame.Frame.from(this._initializer.frame);
if (!frame._page) {
throw new Error([
"Frame for this navigation request is not available, because the request",
"was issued before the frame is created. You can check whether the request",
"is a navigation request by calling isNavigationRequest() method."
].join("\n"));
}
return frame;
}
_safePage() {
return import_frame.Frame.fromNullable(this._initializer.frame)?._page || null;
}
serviceWorker() {
return this._initializer.serviceWorker ? import_worker.Worker.from(this._initializer.serviceWorker) : null;
}
isNavigationRequest() {
return this._initializer.isNavigationRequest;
}
redirectedFrom() {
return this._redirectedFrom;
}
redirectedTo() {
return this._redirectedTo;
}
failure() {
if (this._failureText === null)
return null;
return {
errorText: this._failureText
};
}
timing() {
return this._timing;
}
async sizes() {
const response = await this.response();
if (!response)
throw new Error("Unable to fetch sizes for failed request");
return (await response._channel.sizes()).sizes;
}
_setResponseEndTiming(responseEndTiming) {
this._timing.responseEnd = responseEndTiming;
if (this._timing.responseStart === -1)
this._timing.responseStart = responseEndTiming;
}
_finalRequest() {
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
}
_applyFallbackOverrides(overrides) {
if (overrides.url)
this._fallbackOverrides.url = overrides.url;
if (overrides.method)
this._fallbackOverrides.method = overrides.method;
if (overrides.headers)
this._fallbackOverrides.headers = overrides.headers;
if ((0, import_rtti.isString)(overrides.postData))
this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, "utf-8");
else if (overrides.postData instanceof Buffer)
this._fallbackOverrides.postDataBuffer = overrides.postData;
else if (overrides.postData)
this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), "utf-8");
}
_fallbackOverridesForContinue() {
return this._fallbackOverrides;
}
_targetClosedScope() {
return this.serviceWorker()?._closedScope || this._safePage()?._closedOrCrashedScope || new import_manualPromise.LongStandingScope();
}
}
class Route extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._handlingPromise = null;
this._didThrow = false;
}
static from(route) {
return route._object;
}
request() {
return Request.from(this._initializer.request);
}
async _raceWithTargetClose(promise) {
return await this.request()._targetClosedScope().safeRace(promise);
}
async _startHandling() {
this._handlingPromise = new import_manualPromise.ManualPromise();
return await this._handlingPromise;
}
async fallback(options = {}) {
this._checkNotHandled();
this.request()._applyFallbackOverrides(options);
this._reportHandled(false);
}
async abort(errorCode) {
await this._handleRoute(async () => {
await this._raceWithTargetClose(this._channel.abort({ errorCode }));
});
}
async _redirectNavigationRequest(url) {
await this._handleRoute(async () => {
await this._raceWithTargetClose(this._channel.redirectNavigationRequest({ url }));
});
}
async fetch(options = {}) {
return await this._wrapApiCall(async () => {
return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
});
}
async fulfill(options = {}) {
await this._handleRoute(async () => {
await this._innerFulfill(options);
});
}
async _handleRoute(callback) {
this._checkNotHandled();
try {
await callback();
this._reportHandled(true);
} catch (e) {
this._didThrow = true;
throw e;
}
}
async _innerFulfill(options = {}) {
let fetchResponseUid;
let { status: statusOption, headers: headersOption, body } = options;
if (options.json !== void 0) {
(0, import_assert.assert)(options.body === void 0, "Can specify either body or json parameters");
body = JSON.stringify(options.json);
}
if (options.response instanceof import_fetch.APIResponse) {
statusOption ??= options.response.status();
headersOption ??= options.response.headers();
if (body === void 0 && options.path === void 0) {
if (options.response._request._connection === this._connection)
fetchResponseUid = options.response._fetchUid();
else
body = await options.response.body();
}
}
let isBase64 = false;
let length = 0;
if (options.path) {
const buffer = await this._platform.fs().promises.readFile(options.path);
body = buffer.toString("base64");
isBase64 = true;
length = buffer.length;
} else if ((0, import_rtti.isString)(body)) {
isBase64 = false;
length = Buffer.byteLength(body);
} else if (body) {
length = body.length;
body = body.toString("base64");
isBase64 = true;
}
const headers = {};
for (const header of Object.keys(headersOption || {}))
headers[header.toLowerCase()] = String(headersOption[header]);
if (options.contentType)
headers["content-type"] = String(options.contentType);
else if (options.json)
headers["content-type"] = "application/json";
else if (options.path)
headers["content-type"] = (0, import_mimeType.getMimeTypeForPath)(options.path) || "application/octet-stream";
if (length && !("content-length" in headers))
headers["content-length"] = String(length);
await this._raceWithTargetClose(this._channel.fulfill({
status: statusOption || 200,
headers: (0, import_headers.headersObjectToArray)(headers),
body,
isBase64,
fetchResponseUid
}));
}
async continue(options = {}) {
await this._handleRoute(async () => {
this.request()._applyFallbackOverrides(options);
await this._innerContinue(
false
/* isFallback */
);
});
}
_checkNotHandled() {
if (!this._handlingPromise)
throw new Error("Route is already handled!");
}
_reportHandled(done) {
const chain = this._handlingPromise;
this._handlingPromise = null;
chain.resolve(done);
}
async _innerContinue(isFallback) {
const options = this.request()._fallbackOverridesForContinue();
return await this._raceWithTargetClose(this._channel.continue({
url: options.url,
method: options.method,
headers: options.headers ? (0, import_headers.headersObjectToArray)(options.headers) : void 0,
postData: options.postDataBuffer,
isFallback
}));
}
}
class WebSocketRoute extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._connected = false;
this._server = {
onMessage: (handler) => {
this._onServerMessage = handler;
},
onClose: (handler) => {
this._onServerClose = handler;
},
connectToServer: () => {
throw new Error(`connectToServer must be called on the page-side WebSocketRoute`);
},
url: () => {
return this._initializer.url;
},
close: async (options = {}) => {
await this._channel.closeServer({ ...options, wasClean: true }).catch(() => {
});
},
send: (message) => {
if ((0, import_rtti.isString)(message))
this._channel.sendToServer({ message, isBase64: false }).catch(() => {
});
else
this._channel.sendToServer({ message: message.toString("base64"), isBase64: true }).catch(() => {
});
},
async [Symbol.asyncDispose]() {
await this.close();
}
};
this._channel.on("messageFromPage", ({ message, isBase64 }) => {
if (this._onPageMessage)
this._onPageMessage(isBase64 ? Buffer.from(message, "base64") : message);
else if (this._connected)
this._channel.sendToServer({ message, isBase64 }).catch(() => {
});
});
this._channel.on("messageFromServer", ({ message, isBase64 }) => {
if (this._onServerMessage)
this._onServerMessage(isBase64 ? Buffer.from(message, "base64") : message);
else
this._channel.sendToPage({ message, isBase64 }).catch(() => {
});
});
this._channel.on("closePage", ({ code, reason, wasClean }) => {
if (this._onPageClose)
this._onPageClose(code, reason);
else
this._channel.closeServer({ code, reason, wasClean }).catch(() => {
});
});
this._channel.on("closeServer", ({ code, reason, wasClean }) => {
if (this._onServerClose)
this._onServerClose(code, reason);
else
this._channel.closePage({ code, reason, wasClean }).catch(() => {
});
});
}
static from(route) {
return route._object;
}
url() {
return this._initializer.url;
}
async close(options = {}) {
await this._channel.closePage({ ...options, wasClean: true }).catch(() => {
});
}
connectToServer() {
if (this._connected)
throw new Error("Already connected to the server");
this._connected = true;
this._channel.connect().catch(() => {
});
return this._server;
}
send(message) {
if ((0, import_rtti.isString)(message))
this._channel.sendToPage({ message, isBase64: false }).catch(() => {
});
else
this._channel.sendToPage({ message: message.toString("base64"), isBase64: true }).catch(() => {
});
}
onMessage(handler) {
this._onPageMessage = handler;
}
onClose(handler) {
this._onPageClose = handler;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async _afterHandle() {
if (this._connected)
return;
await this._channel.ensureOpened().catch(() => {
});
}
}
class WebSocketRouteHandler {
constructor(baseURL, url, handler) {
this._baseURL = baseURL;
this.url = url;
this.handler = handler;
}
static prepareInterceptionPatterns(handlers) {
const patterns = [];
let all = false;
for (const handler of handlers) {
const serialized = (0, import_urlMatch.serializeURLMatch)(handler.url);
if (serialized)
patterns.push(serialized);
else
all = true;
}
if (all)
return [{ glob: "**/*" }];
return patterns;
}
matches(wsURL) {
return (0, import_urlMatch.urlMatches)(this._baseURL, wsURL, this.url, true);
}
async handle(webSocketRoute) {
const handler = this.handler;
await handler(webSocketRoute);
await webSocketRoute._afterHandle();
}
}
class Response extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._finishedPromise = new import_manualPromise.ManualPromise();
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._request = Request.from(this._initializer.request);
this._request._response = this;
Object.assign(this._request._timing, this._initializer.timing);
}
static from(response) {
return response._object;
}
static fromNullable(response) {
return response ? Response.from(response) : null;
}
url() {
return this._initializer.url;
}
ok() {
return this._initializer.status === 0 || this._initializer.status >= 200 && this._initializer.status <= 299;
}
status() {
return this._initializer.status;
}
statusText() {
return this._initializer.statusText;
}
fromServiceWorker() {
return this._initializer.fromServiceWorker;
}
/**
* @deprecated
*/
headers() {
return this._provisionalHeaders.headers();
}
async _actualHeaders() {
if (!this._actualHeadersPromise) {
this._actualHeadersPromise = (async () => {
return new RawHeaders((await this._channel.rawResponseHeaders()).headers);
})();
}
return await this._actualHeadersPromise;
}
async allHeaders() {
return (await this._actualHeaders()).headers();
}
async headersArray() {
return (await this._actualHeaders()).headersArray().slice();
}
async headerValue(name) {
return (await this._actualHeaders()).get(name);
}
async headerValues(name) {
return (await this._actualHeaders()).getAll(name);
}
async finished() {
return await this.request()._targetClosedScope().race(this._finishedPromise);
}
async body() {
return (await this._channel.body()).binary;
}
async text() {
const content = await this.body();
return content.toString("utf8");
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
request() {
return this._request;
}
frame() {
return this._request.frame();
}
async serverAddr() {
return (await this._channel.serverAddr()).value || null;
}
async securityDetails() {
return (await this._channel.securityDetails()).value || null;
}
async httpVersion() {
return (await this._channel.httpVersion()).value;
}
}
class WebSocket extends import_channelOwner.ChannelOwner {
static from(webSocket) {
return webSocket._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._isClosed = false;
this._page = parent;
this._channel.on("frameSent", (event) => {
if (event.opcode === 1)
this.emit(import_events.Events.WebSocket.FrameSent, { payload: event.data });
else if (event.opcode === 2)
this.emit(import_events.Events.WebSocket.FrameSent, { payload: Buffer.from(event.data, "base64") });
});
this._channel.on("frameReceived", (event) => {
if (event.opcode === 1)
this.emit(import_events.Events.WebSocket.FrameReceived, { payload: event.data });
else if (event.opcode === 2)
this.emit(import_events.Events.WebSocket.FrameReceived, { payload: Buffer.from(event.data, "base64") });
});
this._channel.on("socketError", ({ error }) => this.emit(import_events.Events.WebSocket.Error, error));
this._channel.on("close", () => {
this._isClosed = true;
this.emit(import_events.Events.WebSocket.Close, this);
});
}
url() {
return this._initializer.url;
}
isClosed() {
return this._isClosed;
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.WebSocket.Error)
waiter.rejectOnEvent(this, import_events.Events.WebSocket.Error, new Error("Socket error"));
if (event !== import_events.Events.WebSocket.Close)
waiter.rejectOnEvent(this, import_events.Events.WebSocket.Close, new Error("Socket closed"));
waiter.rejectOnEvent(this._page, import_events.Events.Page.Close, () => this._page._closeErrorWithReason());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
function validateHeaders(headers) {
for (const key of Object.keys(headers)) {
const value = headers[key];
if (!Object.is(value, void 0) && !(0, import_rtti.isString)(value))
throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
}
}
class RouteHandler {
constructor(platform, baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
this.handledCount = 0;
this._ignoreException = false;
this._activeInvocations = /* @__PURE__ */ new Set();
this._baseURL = baseURL;
this._times = times;
this.url = url;
this.handler = handler;
this._savedZone = platform.zones.current().pop();
}
static prepareInterceptionPatterns(handlers) {
const patterns = [];
let all = false;
for (const handler of handlers) {
const serialized = (0, import_urlMatch.serializeURLMatch)(handler.url);
if (serialized)
patterns.push(serialized);
else
all = true;
}
if (all)
return [{ glob: "**/*" }];
return patterns;
}
matches(requestURL) {
return (0, import_urlMatch.urlMatches)(this._baseURL, requestURL, this.url);
}
async handle(route) {
return await this._savedZone.run(async () => this._handleImpl(route));
}
async _handleImpl(route) {
const handlerInvocation = { complete: new import_manualPromise.ManualPromise(), route };
this._activeInvocations.add(handlerInvocation);
try {
return await this._handleInternal(route);
} catch (e) {
if (this._ignoreException)
return false;
if ((0, import_errors.isTargetClosedError)(e)) {
(0, import_stackTrace.rewriteErrorMessage)(e, `"${e.message}" while running route callback.
Consider awaiting \`await page.unrouteAll({ behavior: 'ignoreErrors' })\`
before the end of the test to ignore remaining routes in flight.`);
}
throw e;
} finally {
handlerInvocation.complete.resolve();
this._activeInvocations.delete(handlerInvocation);
}
}
async stop(behavior) {
if (behavior === "ignoreErrors") {
this._ignoreException = true;
} else {
const promises = [];
for (const activation of this._activeInvocations) {
if (!activation.route._didThrow)
promises.push(activation.complete);
}
await Promise.all(promises);
}
}
async _handleInternal(route) {
++this.handledCount;
const handledPromise = route._startHandling();
const handler = this.handler;
const [handled] = await Promise.all([
handledPromise,
handler(route, route.request())
]);
return handled;
}
willExpire() {
return this.handledCount + 1 >= this._times;
}
}
class RawHeaders {
constructor(headers) {
this._headersMap = new import_multimap.MultiMap();
this._headersArray = headers;
for (const header of headers)
this._headersMap.set(header.name.toLowerCase(), header.value);
}
static _fromHeadersObjectLossy(headers) {
const headersArray = Object.entries(headers).map(([name, value]) => ({
name,
value
})).filter((header) => header.value !== void 0);
return new RawHeaders(headersArray);
}
get(name) {
const values = this.getAll(name);
if (!values || !values.length)
return null;
return values.join(name.toLowerCase() === "set-cookie" ? "\n" : ", ");
}
getAll(name) {
return [...this._headersMap.get(name.toLowerCase())];
}
headers() {
const result = {};
for (const name of this._headersMap.keys())
result[name] = this.get(name);
return result;
}
headersArray() {
return this._headersArray;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
RawHeaders,
Request,
Response,
Route,
RouteHandler,
WebSocket,
WebSocketRoute,
WebSocketRouteHandler,
validateHeaders
});
@@ -0,0 +1,731 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var page_exports = {};
__export(page_exports, {
BindingCall: () => BindingCall,
Page: () => Page
});
module.exports = __toCommonJS(page_exports);
var import_artifact = require("./artifact");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_coverage = require("./coverage");
var import_disposable = require("./disposable");
var import_download = require("./download");
var import_elementHandle = require("./elementHandle");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fileChooser = require("./fileChooser");
var import_frame = require("./frame");
var import_harRouter = require("./harRouter");
var import_input = require("./input");
var import_jsHandle = require("./jsHandle");
var import_network = require("./network");
var import_video = require("./video");
var import_screencast = require("./screencast");
var import_waiter = require("./waiter");
var import_worker = require("./worker");
var import_timeoutSettings = require("./timeoutSettings");
var import_assert = require("../utils/isomorphic/assert");
var import_fileUtils = require("./fileUtils");
var import_headers = require("../utils/isomorphic/headers");
var import_stringUtils = require("../utils/isomorphic/stringUtils");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_manualPromise = require("../utils/isomorphic/manualPromise");
var import_rtti = require("../utils/isomorphic/rtti");
var import_consoleMessage = require("./consoleMessage");
class Page extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._frames = /* @__PURE__ */ new Set();
this._workers = /* @__PURE__ */ new Set();
this._closed = false;
this._closedOrCrashedScope = new import_manualPromise.LongStandingScope();
this._routes = [];
this._webSocketRoutes = [];
this._bindings = /* @__PURE__ */ new Map();
this._closeWasCalled = false;
this._harRouters = [];
this._locatorHandlers = /* @__PURE__ */ new Map();
this._instrumentation.onPage(this);
this._browserContext = parent;
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, this._browserContext._timeoutSettings);
this.keyboard = new import_input.Keyboard(this);
this.mouse = new import_input.Mouse(this);
this.request = this._browserContext.request;
this.touchscreen = new import_input.Touchscreen(this);
this.clock = this._browserContext.clock;
this._mainFrame = import_frame.Frame.from(initializer.mainFrame);
this._mainFrame._page = this;
this._frames.add(this._mainFrame);
this._viewportSize = initializer.viewportSize;
this._closed = initializer.isClosed;
this._opener = Page.fromNullable(initializer.opener);
this._video = new import_video.Video(this, this._connection, initializer.video ? import_artifact.Artifact.from(initializer.video) : void 0);
this.screencast = new import_screencast.Screencast(this);
this._channel.on("bindingCall", ({ binding }) => this._onBinding(BindingCall.from(binding)));
this._channel.on("close", () => this._onClose());
this._channel.on("crash", () => this._onCrash());
this._channel.on("download", ({ url, suggestedFilename, artifact }) => {
const artifactObject = import_artifact.Artifact.from(artifact);
this.emit(import_events.Events.Page.Download, new import_download.Download(this, url, suggestedFilename, artifactObject));
});
this._channel.on("fileChooser", ({ element, isMultiple }) => this.emit(import_events.Events.Page.FileChooser, new import_fileChooser.FileChooser(this, import_elementHandle.ElementHandle.from(element), isMultiple)));
this._channel.on("frameAttached", ({ frame }) => this._onFrameAttached(import_frame.Frame.from(frame)));
this._channel.on("frameDetached", ({ frame }) => this._onFrameDetached(import_frame.Frame.from(frame)));
this._channel.on("locatorHandlerTriggered", ({ uid }) => this._onLocatorHandlerTriggered(uid));
this._channel.on("route", ({ route }) => this._onRoute(import_network.Route.from(route)));
this._channel.on("webSocketRoute", ({ webSocketRoute }) => this._onWebSocketRoute(import_network.WebSocketRoute.from(webSocketRoute)));
this._channel.on("viewportSizeChanged", ({ viewportSize }) => this._viewportSize = viewportSize);
this._channel.on("webSocket", ({ webSocket }) => this.emit(import_events.Events.Page.WebSocket, import_network.WebSocket.from(webSocket)));
this._channel.on("worker", ({ worker }) => this._onWorker(import_worker.Worker.from(worker)));
this.coverage = new import_coverage.Coverage(this._channel);
this.once(import_events.Events.Page.Close, () => this._closedOrCrashedScope.close(this._closeErrorWithReason()));
this.once(import_events.Events.Page.Crash, () => this._closedOrCrashedScope.close(new import_errors.TargetClosedError()));
this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
[import_events.Events.Page.Console, "console"],
[import_events.Events.Page.Dialog, "dialog"],
[import_events.Events.Page.Request, "request"],
[import_events.Events.Page.Response, "response"],
[import_events.Events.Page.RequestFinished, "requestFinished"],
[import_events.Events.Page.RequestFailed, "requestFailed"],
[import_events.Events.Page.FileChooser, "fileChooser"]
]));
}
static from(page) {
return page._object;
}
static fromNullable(page) {
return page ? Page.from(page) : null;
}
_onFrameAttached(frame) {
frame._page = this;
this._frames.add(frame);
if (frame._parentFrame)
frame._parentFrame._childFrames.add(frame);
this.emit(import_events.Events.Page.FrameAttached, frame);
}
_onFrameDetached(frame) {
this._frames.delete(frame);
frame._detached = true;
if (frame._parentFrame)
frame._parentFrame._childFrames.delete(frame);
this.emit(import_events.Events.Page.FrameDetached, frame);
}
async _onRoute(route) {
route._context = this.context();
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (this._closeWasCalled || this._browserContext.isClosed())
return;
if (!routeHandler.matches(route.request().url()))
continue;
const index = this._routes.indexOf(routeHandler);
if (index === -1)
continue;
if (routeHandler.willExpire())
this._routes.splice(index, 1);
const handled = await routeHandler.handle(route);
if (!this._routes.length)
this._updateInterceptionPatterns({ internal: true }).catch(() => {
});
if (handled)
return;
}
await this._browserContext._onRoute(route);
}
async _onWebSocketRoute(webSocketRoute) {
const routeHandler = this._webSocketRoutes.find((route) => route.matches(webSocketRoute.url()));
if (routeHandler)
await routeHandler.handle(webSocketRoute);
else
await this._browserContext._onWebSocketRoute(webSocketRoute);
}
async _onBinding(bindingCall) {
const func = this._bindings.get(bindingCall._initializer.name);
if (func) {
await bindingCall.call(func);
return;
}
await this._browserContext._onBinding(bindingCall);
}
_onWorker(worker) {
this._workers.add(worker);
worker._page = this;
this.emit(import_events.Events.Page.Worker, worker);
}
_onClose() {
this._closed = true;
this._browserContext._pages.delete(this);
this._disposeHarRouters();
this.emit(import_events.Events.Page.Close, this);
}
_onCrash() {
this.emit(import_events.Events.Page.Crash, this);
}
context() {
return this._browserContext;
}
async opener() {
if (!this._opener || this._opener.isClosed())
return null;
return this._opener;
}
mainFrame() {
return this._mainFrame;
}
frame(frameSelector) {
const name = (0, import_rtti.isString)(frameSelector) ? frameSelector : frameSelector.name;
const url = (0, import_rtti.isObject)(frameSelector) ? frameSelector.url : void 0;
(0, import_assert.assert)(name || url, "Either name or url matcher should be specified");
return this.frames().find((f) => {
if (name)
return f.name() === name;
return (0, import_urlMatch.urlMatches)(this._browserContext._options.baseURL, f.url(), url);
}) || null;
}
frames() {
return [...this._frames];
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
video() {
if (!this._browserContext._options.recordVideo)
return null;
return this._video;
}
async pickLocator() {
const { selector } = await this._channel.pickLocator({});
return this.locator(selector);
}
async cancelPickLocator() {
await this._channel.cancelPickLocator({});
}
async $(selector, options) {
return await this._mainFrame.$(selector, options);
}
async waitForSelector(selector, options) {
return await this._mainFrame.waitForSelector(selector, options);
}
async dispatchEvent(selector, type, eventInit, options) {
return await this._mainFrame.dispatchEvent(selector, type, eventInit, options);
}
async evaluateHandle(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
return await this._mainFrame.evaluateHandle(pageFunction, arg);
}
async $eval(selector, pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
return await this._mainFrame.$eval(selector, pageFunction, arg);
}
async $$eval(selector, pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
return await this._mainFrame.$$eval(selector, pageFunction, arg);
}
async $$(selector) {
return await this._mainFrame.$$(selector);
}
async addScriptTag(options = {}) {
return await this._mainFrame.addScriptTag(options);
}
async addStyleTag(options = {}) {
return await this._mainFrame.addStyleTag(options);
}
async exposeFunction(name, callback) {
const result = await this._channel.exposeBinding({ name });
const binding = (source, ...args) => callback(...args);
this._bindings.set(name, binding);
return import_disposable.DisposableObject.from(result.disposable);
}
async exposeBinding(name, callback, options = {}) {
const result = await this._channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback);
return import_disposable.DisposableObject.from(result.disposable);
}
async setExtraHTTPHeaders(headers) {
(0, import_network.validateHeaders)(headers);
await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
}
url() {
return this._mainFrame.url();
}
async content() {
return await this._mainFrame.content();
}
async setContent(html, options) {
return await this._mainFrame.setContent(html, options);
}
async goto(url, options) {
return await this._mainFrame.goto(url, options);
}
async reload(options = {}) {
const waitUntil = (0, import_frame.verifyLoadState)("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
return import_network.Response.fromNullable((await this._channel.reload({ ...options, waitUntil, timeout: this._timeoutSettings.navigationTimeout(options) })).response);
}
async addLocatorHandler(locator, handler, options = {}) {
if (locator._frame !== this._mainFrame)
throw new Error(`Locator must belong to the main frame of this page`);
if (options.times === 0)
return;
const { uid } = await this._channel.registerLocatorHandler({ selector: locator._selector, noWaitAfter: options.noWaitAfter });
this._locatorHandlers.set(uid, { locator, handler, times: options.times });
}
async _onLocatorHandlerTriggered(uid) {
let remove = false;
try {
const handler = this._locatorHandlers.get(uid);
if (handler && handler.times !== 0) {
if (handler.times !== void 0)
handler.times--;
await handler.handler(handler.locator);
}
remove = handler?.times === 0;
} finally {
if (remove)
this._locatorHandlers.delete(uid);
this._channel.resolveLocatorHandlerNoReply({ uid, remove }).catch(() => {
});
}
}
async removeLocatorHandler(locator) {
for (const [uid, data] of this._locatorHandlers) {
if (data.locator._equals(locator)) {
this._locatorHandlers.delete(uid);
await this._channel.unregisterLocatorHandler({ uid }).catch(() => {
});
}
}
}
async waitForLoadState(state, options) {
return await this._mainFrame.waitForLoadState(state, options);
}
async waitForNavigation(options) {
return await this._mainFrame.waitForNavigation(options);
}
async waitForURL(url, options) {
return await this._mainFrame.waitForURL(url, options);
}
async waitForRequest(urlOrPredicate, options = {}) {
const predicate = async (request) => {
if ((0, import_rtti.isString)(urlOrPredicate) || (0, import_rtti.isRegExp)(urlOrPredicate))
return (0, import_urlMatch.urlMatches)(this._browserContext._options.baseURL, request.url(), urlOrPredicate);
return await urlOrPredicate(request);
};
const trimmedUrl = trimUrl(urlOrPredicate);
const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : void 0;
return await this._waitForEvent(import_events.Events.Page.Request, { predicate, timeout: options.timeout }, logLine);
}
async waitForResponse(urlOrPredicate, options = {}) {
const predicate = async (response) => {
if ((0, import_rtti.isString)(urlOrPredicate) || (0, import_rtti.isRegExp)(urlOrPredicate))
return (0, import_urlMatch.urlMatches)(this._browserContext._options.baseURL, response.url(), urlOrPredicate);
return await urlOrPredicate(response);
};
const trimmedUrl = trimUrl(urlOrPredicate);
const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : void 0;
return await this._waitForEvent(import_events.Events.Page.Response, { predicate, timeout: options.timeout }, logLine);
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
}
_closeErrorWithReason() {
return new import_errors.TargetClosedError(this._closeReason || this._browserContext._effectiveCloseReason());
}
async _waitForEvent(event, optionsOrPredicate, logLine) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
if (logLine)
waiter.log(logLine);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.Page.Crash)
waiter.rejectOnEvent(this, import_events.Events.Page.Crash, new Error("Page crashed"));
if (event !== import_events.Events.Page.Close)
waiter.rejectOnEvent(this, import_events.Events.Page.Close, () => this._closeErrorWithReason());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async goBack(options = {}) {
const waitUntil = (0, import_frame.verifyLoadState)("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
return import_network.Response.fromNullable((await this._channel.goBack({ ...options, waitUntil, timeout: this._timeoutSettings.navigationTimeout(options) })).response);
}
async goForward(options = {}) {
const waitUntil = (0, import_frame.verifyLoadState)("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
return import_network.Response.fromNullable((await this._channel.goForward({ ...options, waitUntil, timeout: this._timeoutSettings.navigationTimeout(options) })).response);
}
async requestGC() {
await this._channel.requestGC();
}
async emulateMedia(options = {}) {
await this._channel.emulateMedia({
media: options.media === null ? "no-override" : options.media,
colorScheme: options.colorScheme === null ? "no-override" : options.colorScheme,
reducedMotion: options.reducedMotion === null ? "no-override" : options.reducedMotion,
forcedColors: options.forcedColors === null ? "no-override" : options.forcedColors,
contrast: options.contrast === null ? "no-override" : options.contrast
});
}
async setViewportSize(viewportSize) {
this._viewportSize = viewportSize;
await this._channel.setViewportSize({ viewportSize });
}
viewportSize() {
return this._viewportSize || null;
}
async evaluate(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
return await this._mainFrame.evaluate(pageFunction, arg);
}
async addInitScript(script, arg) {
const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, arg);
return import_disposable.DisposableObject.from((await this._channel.addInitScript({ source })).disposable);
}
async route(url, handler, options = {}) {
this._routes.unshift(new import_network.RouteHandler(this._platform, this._browserContext._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns({ title: "Route requests" });
return new import_disposable.DisposableStub(() => this.unroute(url, handler));
}
async routeFromHAR(har, options = {}) {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error("Route from har is not supported in thin clients");
if (options.update) {
await this._browserContext._recordIntoHAR(har, this, options);
return;
}
const harRouter = await import_harRouter.HarRouter.create(localUtils, har, options.notFound || "abort", { urlMatch: options.url });
this._harRouters.push(harRouter);
await harRouter.addPageRoute(this);
}
async routeWebSocket(url, handler) {
this._webSocketRoutes.unshift(new import_network.WebSocketRouteHandler(this._browserContext._options.baseURL, url, handler));
await this._updateWebSocketInterceptionPatterns({ title: "Route WebSockets" });
}
_disposeHarRouters() {
this._harRouters.forEach((router) => router.dispose());
this._harRouters = [];
}
async unrouteAll(options) {
await this._unrouteInternal(this._routes, [], options?.behavior);
this._disposeHarRouters();
}
async unroute(url, handler) {
const removed = [];
const remaining = [];
for (const route of this._routes) {
if ((0, import_urlMatch.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler))
removed.push(route);
else
remaining.push(route);
}
await this._unrouteInternal(removed, remaining, "default");
}
async _unrouteInternal(removed, remaining, behavior) {
this._routes = remaining;
if (behavior && behavior !== "default") {
const promises = removed.map((routeHandler) => routeHandler.stop(behavior));
await Promise.all(promises);
}
await this._updateInterceptionPatterns({ title: "Unroute requests" });
}
async _updateInterceptionPatterns(options) {
const patterns = import_network.RouteHandler.prepareInterceptionPatterns(this._routes);
await this._wrapApiCall(() => this._channel.setNetworkInterceptionPatterns({ patterns }), options);
}
async _updateWebSocketInterceptionPatterns(options) {
const patterns = import_network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
await this._wrapApiCall(() => this._channel.setWebSocketInterceptionPatterns({ patterns }), options);
}
async screenshot(options = {}) {
const mask = options.mask;
const copy = { ...options, mask: void 0, timeout: this._timeoutSettings.timeout(options) };
if (!copy.type)
copy.type = (0, import_elementHandle.determineScreenshotType)(options);
if (mask) {
copy.mask = mask.map((locator) => ({
frame: locator._frame._channel,
selector: locator._selector
}));
}
const result = await this._channel.screenshot(copy);
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, result.binary);
}
return result.binary;
}
async _expectScreenshot(options) {
const mask = options?.mask ? options?.mask.map((locator2) => ({
frame: locator2._frame._channel,
selector: locator2._selector
})) : void 0;
const locator = options.locator ? {
frame: options.locator._frame._channel,
selector: options.locator._selector
} : void 0;
return await this._channel.expectScreenshot({
...options,
isNot: !!options.isNot,
locator,
mask
});
}
async title() {
return await this._mainFrame.title();
}
async bringToFront() {
await this._channel.bringToFront();
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close(options = {}) {
this._closeReason = options.reason;
if (!options.runBeforeUnload)
this._closeWasCalled = true;
try {
if (this._ownedContext)
await this._ownedContext.close();
else
await this._channel.close(options);
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e) && !options.runBeforeUnload)
return;
throw e;
}
}
isClosed() {
return this._closed;
}
async click(selector, options) {
return await this._mainFrame.click(selector, options);
}
async dragAndDrop(source, target, options) {
return await this._mainFrame.dragAndDrop(source, target, options);
}
async dblclick(selector, options) {
await this._mainFrame.dblclick(selector, options);
}
async tap(selector, options) {
return await this._mainFrame.tap(selector, options);
}
async fill(selector, value, options) {
return await this._mainFrame.fill(selector, value, options);
}
async clearConsoleMessages() {
await this._channel.clearConsoleMessages();
}
async consoleMessages(options) {
const { messages } = await this._channel.consoleMessages({ filter: options?.filter });
return messages.map((message) => new import_consoleMessage.ConsoleMessage(this._platform, message, this, null));
}
async clearPageErrors() {
await this._channel.clearPageErrors();
}
async pageErrors(options) {
const { errors } = await this._channel.pageErrors({ filter: options?.filter });
return errors.map((error) => (0, import_errors.parseError)(error));
}
locator(selector, options) {
return this.mainFrame().locator(selector, options);
}
getByTestId(testId) {
return this.mainFrame().getByTestId(testId);
}
getByAltText(text, options) {
return this.mainFrame().getByAltText(text, options);
}
getByLabel(text, options) {
return this.mainFrame().getByLabel(text, options);
}
getByPlaceholder(text, options) {
return this.mainFrame().getByPlaceholder(text, options);
}
getByText(text, options) {
return this.mainFrame().getByText(text, options);
}
getByTitle(text, options) {
return this.mainFrame().getByTitle(text, options);
}
getByRole(role, options = {}) {
return this.mainFrame().getByRole(role, options);
}
frameLocator(selector) {
return this.mainFrame().frameLocator(selector);
}
async focus(selector, options) {
return await this._mainFrame.focus(selector, options);
}
async textContent(selector, options) {
return await this._mainFrame.textContent(selector, options);
}
async innerText(selector, options) {
return await this._mainFrame.innerText(selector, options);
}
async innerHTML(selector, options) {
return await this._mainFrame.innerHTML(selector, options);
}
async getAttribute(selector, name, options) {
return await this._mainFrame.getAttribute(selector, name, options);
}
async inputValue(selector, options) {
return await this._mainFrame.inputValue(selector, options);
}
async isChecked(selector, options) {
return await this._mainFrame.isChecked(selector, options);
}
async isDisabled(selector, options) {
return await this._mainFrame.isDisabled(selector, options);
}
async isEditable(selector, options) {
return await this._mainFrame.isEditable(selector, options);
}
async isEnabled(selector, options) {
return await this._mainFrame.isEnabled(selector, options);
}
async isHidden(selector, options) {
return await this._mainFrame.isHidden(selector, options);
}
async isVisible(selector, options) {
return await this._mainFrame.isVisible(selector, options);
}
async hover(selector, options) {
return await this._mainFrame.hover(selector, options);
}
async selectOption(selector, values, options) {
return await this._mainFrame.selectOption(selector, values, options);
}
async setInputFiles(selector, files, options) {
return await this._mainFrame.setInputFiles(selector, files, options);
}
async type(selector, text, options) {
return await this._mainFrame.type(selector, text, options);
}
async press(selector, key, options) {
return await this._mainFrame.press(selector, key, options);
}
async check(selector, options) {
return await this._mainFrame.check(selector, options);
}
async uncheck(selector, options) {
return await this._mainFrame.uncheck(selector, options);
}
async setChecked(selector, checked, options) {
return await this._mainFrame.setChecked(selector, checked, options);
}
async waitForTimeout(timeout) {
return await this._mainFrame.waitForTimeout(timeout);
}
async waitForFunction(pageFunction, arg, options) {
return await this._mainFrame.waitForFunction(pageFunction, arg, options);
}
async requests() {
const { requests } = await this._channel.requests();
return requests.map((request) => import_network.Request.from(request));
}
workers() {
return [...this._workers];
}
async pause(_options) {
if (this._platform.isJSDebuggerAttached())
return;
const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();
this._browserContext.setDefaultNavigationTimeout(0);
this._browserContext.setDefaultTimeout(0);
this._instrumentation?.onWillPause({ keepTestTimeout: !!_options?.__testHookKeepTestTimeout });
await this._closedOrCrashedScope.safeRace(this.context()._channel.pause());
this._browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
this._browserContext.setDefaultTimeout(defaultTimeout);
}
async pdf(options = {}) {
const transportOptions = { ...options };
if (transportOptions.margin)
transportOptions.margin = { ...transportOptions.margin };
if (typeof options.width === "number")
transportOptions.width = options.width + "px";
if (typeof options.height === "number")
transportOptions.height = options.height + "px";
for (const margin of ["top", "right", "bottom", "left"]) {
const index = margin;
if (options.margin && typeof options.margin[index] === "number")
transportOptions.margin[index] = transportOptions.margin[index] + "px";
}
const result = await this._channel.pdf(transportOptions);
if (options.path) {
const platform = this._platform;
await platform.fs().promises.mkdir(platform.path().dirname(options.path), { recursive: true });
await platform.fs().promises.writeFile(options.path, result.pdf);
}
return result.pdf;
}
async ariaSnapshot(options = {}) {
const result = await this.mainFrame()._channel.ariaSnapshot({ timeout: this._timeoutSettings.timeout(options), track: options._track, mode: options.mode, depth: options.depth });
return result.snapshot;
}
async _setDockTile(image) {
await this._channel.setDockTile({ image });
}
}
class BindingCall extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
async call(func) {
try {
const frame = import_frame.Frame.from(this._initializer.frame);
const source = {
context: frame._page.context(),
page: frame._page,
frame
};
let result;
if (this._initializer.handle)
result = await func(source, import_jsHandle.JSHandle.from(this._initializer.handle));
else
result = await func(source, ...this._initializer.args.map(import_jsHandle.parseResult));
this._channel.resolve({ result: (0, import_jsHandle.serializeArgument)(result) }).catch(() => {
});
} catch (e) {
this._channel.reject({ error: (0, import_errors.serializeError)(e) }).catch(() => {
});
}
}
}
function trimUrl(param) {
if ((0, import_rtti.isRegExp)(param))
return `/${(0, import_stringUtils.trimStringWithEllipsis)(param.source, 50)}/${param.flags}`;
if ((0, import_rtti.isString)(param))
return `"${(0, import_stringUtils.trimStringWithEllipsis)(param, 50)}"`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BindingCall,
Page
});
@@ -0,0 +1,74 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var platform_exports = {};
__export(platform_exports, {
emptyPlatform: () => emptyPlatform
});
module.exports = __toCommonJS(platform_exports);
var import_colors = require("../utils/isomorphic/colors");
const noopZone = {
push: () => noopZone,
pop: () => noopZone,
run: (func) => func(),
data: () => void 0
};
const emptyPlatform = {
name: "empty",
boxedStackPrefixes: () => [],
calculateSha1: async () => {
throw new Error("Not implemented");
},
colors: import_colors.webColors,
createGuid: () => {
throw new Error("Not implemented");
},
defaultMaxListeners: () => 10,
env: {},
fs: () => {
throw new Error("Not implemented");
},
inspectCustom: void 0,
isDebugMode: () => false,
isJSDebuggerAttached: () => false,
isLogEnabled(name) {
return false;
},
isUnderTest: () => false,
log(name, message) {
},
path: () => {
throw new Error("Function not implemented.");
},
pathSeparator: "/",
showInternalStackFrames: () => false,
streamFile(path, writable) {
throw new Error("Streams are not available");
},
streamReadable: (channel) => {
throw new Error("Streams are not available");
},
streamWritable: (channel) => {
throw new Error("Streams are not available");
},
zones: { empty: noopZone, current: () => noopZone }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
emptyPlatform
});
@@ -0,0 +1,71 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var playwright_exports = {};
__export(playwright_exports, {
Playwright: () => Playwright
});
module.exports = __toCommonJS(playwright_exports);
var import_android = require("./android");
var import_browser = require("./browser");
var import_browserType = require("./browserType");
var import_channelOwner = require("./channelOwner");
var import_electron = require("./electron");
var import_errors = require("./errors");
var import_fetch = require("./fetch");
var import_selectors = require("./selectors");
class Playwright extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this.request = new import_fetch.APIRequest(this);
this.chromium = import_browserType.BrowserType.from(initializer.chromium);
this.chromium._playwright = this;
this.firefox = import_browserType.BrowserType.from(initializer.firefox);
this.firefox._playwright = this;
this.webkit = import_browserType.BrowserType.from(initializer.webkit);
this.webkit._playwright = this;
this._android = import_android.Android.from(initializer.android);
this._android._playwright = this;
this._electron = import_electron.Electron.from(initializer.electron);
this._electron._playwright = this;
this.devices = this._connection.localUtils()?.devices ?? {};
this.selectors = new import_selectors.Selectors(this._connection._platform);
this.errors = { TimeoutError: import_errors.TimeoutError };
}
static from(channel) {
return channel._object;
}
_browserTypes() {
return [this.chromium, this.firefox, this.webkit];
}
_preLaunchedBrowser() {
const browser = import_browser.Browser.from(this._initializer.preLaunchedBrowser);
browser._connectToBrowserType(this[browser._name], {}, void 0);
return browser;
}
_allContexts() {
return this._browserTypes().flatMap((type) => [...type._contexts]);
}
_allPages() {
return this._allContexts().flatMap((context) => context.pages());
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Playwright
});
@@ -0,0 +1,88 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var screencast_exports = {};
__export(screencast_exports, {
Screencast: () => Screencast
});
module.exports = __toCommonJS(screencast_exports);
var import_artifact = require("./artifact");
var import_disposable = require("./disposable");
class Screencast {
constructor(page) {
this._started = false;
this._onFrame = null;
this._page = page;
this._page._channel.on("screencastFrame", ({ data }) => {
void this._onFrame?.({ data });
});
}
async start(options = {}) {
if (this._started)
throw new Error("Screencast is already started");
this._started = true;
if (options.onFrame)
this._onFrame = options.onFrame;
const result = await this._page._channel.screencastStart({
size: options.size,
quality: options.quality,
sendFrames: !!options.onFrame,
record: !!options.path
});
if (result.artifact) {
this._artifact = import_artifact.Artifact.from(result.artifact);
this._savePath = options.path;
}
return new import_disposable.DisposableStub(() => this.stop());
}
async stop() {
await this._page._wrapApiCall(async () => {
this._started = false;
this._onFrame = null;
await this._page._channel.screencastStop();
if (this._savePath)
await this._artifact?.saveAs(this._savePath);
this._artifact = void 0;
this._savePath = void 0;
});
}
async showActions(options) {
await this._page._channel.screencastShowActions({ duration: options?.duration, position: options?.position, fontSize: options?.fontSize });
return new import_disposable.DisposableStub(() => this._page._channel.screencastHideActions());
}
async hideActions() {
await this._page._channel.screencastHideActions();
}
async showOverlay(html, options) {
const { id } = await this._page._channel.screencastShowOverlay({ html, duration: options?.duration });
return new import_disposable.DisposableStub(() => this._page._channel.screencastRemoveOverlay({ id }));
}
async showChapter(title, options) {
await this._page._channel.screencastChapter({ title, ...options });
}
async showOverlays() {
await this._page._channel.screencastSetOverlayVisible({ visible: true });
}
async hideOverlays() {
await this._page._channel.screencastSetOverlayVisible({ visible: false });
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Screencast
});
@@ -0,0 +1,57 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var selectors_exports = {};
__export(selectors_exports, {
Selectors: () => Selectors
});
module.exports = __toCommonJS(selectors_exports);
var import_clientHelper = require("./clientHelper");
var import_locator = require("./locator");
class Selectors {
constructor(platform) {
this._selectorEngines = [];
this._contextsForSelectors = /* @__PURE__ */ new Set();
this._platform = platform;
}
async register(name, script, options = {}) {
if (this._selectorEngines.some((engine) => engine.name === name))
throw new Error(`selectors.register: "${name}" selector engine has been already registered`);
const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, void 0, false);
const selectorEngine = { ...options, name, source };
for (const context of this._contextsForSelectors)
await context._channel.registerSelectorEngine({ selectorEngine });
this._selectorEngines.push(selectorEngine);
}
setTestIdAttribute(attributeName) {
this._testIdAttributeName = attributeName;
(0, import_locator.setTestIdAttribute)(attributeName);
for (const context of this._contextsForSelectors) {
context._options.testIdAttributeName = attributeName;
context._channel.setTestIdAttributeName({ testIdAttributeName: attributeName }).catch(() => {
});
}
}
_withSelectorOptions(options) {
return { ...options, selectorEngines: this._selectorEngines, testIdAttributeName: this._testIdAttributeName };
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Selectors
});
@@ -0,0 +1,39 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var stream_exports = {};
__export(stream_exports, {
Stream: () => Stream
});
module.exports = __toCommonJS(stream_exports);
var import_channelOwner = require("./channelOwner");
class Stream extends import_channelOwner.ChannelOwner {
static from(Stream2) {
return Stream2._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
stream() {
return this._platform.streamReadable(this._channel);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Stream
});
@@ -0,0 +1,79 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var timeoutSettings_exports = {};
__export(timeoutSettings_exports, {
TimeoutSettings: () => TimeoutSettings
});
module.exports = __toCommonJS(timeoutSettings_exports);
var import_time = require("../utils/isomorphic/time");
class TimeoutSettings {
constructor(platform, parent) {
this._parent = parent;
this._platform = platform;
}
setDefaultTimeout(timeout) {
this._defaultTimeout = timeout;
}
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
defaultNavigationTimeout() {
return this._defaultNavigationTimeout;
}
defaultTimeout() {
return this._defaultTimeout;
}
navigationTimeout(options) {
if (typeof options.timeout === "number")
return options.timeout;
if (this._defaultNavigationTimeout !== void 0)
return this._defaultNavigationTimeout;
if (this._platform.isDebugMode())
return 0;
if (this._defaultTimeout !== void 0)
return this._defaultTimeout;
if (this._parent)
return this._parent.navigationTimeout(options);
return import_time.DEFAULT_PLAYWRIGHT_TIMEOUT;
}
timeout(options) {
if (typeof options.timeout === "number")
return options.timeout;
if (this._platform.isDebugMode())
return 0;
if (this._defaultTimeout !== void 0)
return this._defaultTimeout;
if (this._parent)
return this._parent.timeout(options);
return import_time.DEFAULT_PLAYWRIGHT_TIMEOUT;
}
launchTimeout(options) {
if (typeof options.timeout === "number")
return options.timeout;
if (this._platform.isDebugMode())
return 0;
if (this._parent)
return this._parent.launchTimeout(options);
return import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TimeoutSettings
});
@@ -0,0 +1,126 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var tracing_exports = {};
__export(tracing_exports, {
Tracing: () => Tracing
});
module.exports = __toCommonJS(tracing_exports);
var import_artifact = require("./artifact");
var import_channelOwner = require("./channelOwner");
var import_disposable = require("./disposable");
class Tracing extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._includeSources = false;
this._additionalSources = /* @__PURE__ */ new Set();
this._isLive = false;
this._isTracing = false;
}
static from(channel) {
return channel._object;
}
async start(options = {}) {
await this._wrapApiCall(async () => {
this._includeSources = !!options.sources;
this._isLive = !!options.live;
await this._channel.tracingStart({
name: options.name,
snapshots: options.snapshots,
screenshots: options.screenshots,
live: options.live
});
const { traceName } = await this._channel.tracingStartChunk({ name: options.name, title: options.title });
await this._startCollectingStacks(traceName, this._isLive);
});
}
async startChunk(options = {}) {
await this._wrapApiCall(async () => {
const { traceName } = await this._channel.tracingStartChunk(options);
await this._startCollectingStacks(traceName, this._isLive);
});
}
async group(name, options = {}) {
if (options.location)
this._additionalSources.add(options.location.file);
await this._channel.tracingGroup({ name, location: options.location });
return new import_disposable.DisposableStub(() => this.groupEnd());
}
async groupEnd() {
await this._channel.tracingGroupEnd();
}
async _startCollectingStacks(traceName, live) {
if (!this._isTracing) {
this._isTracing = true;
this._connection.setIsTracing(true);
}
const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName, live });
this._stacksId = result?.stacksId;
}
async stopChunk(options = {}) {
await this._wrapApiCall(async () => {
await this._doStopChunk(options.path);
});
}
async stop(options = {}) {
await this._wrapApiCall(async () => {
await this._doStopChunk(options.path);
await this._channel.tracingStop();
});
}
async _doStopChunk(filePath) {
this._resetStackCounter();
const additionalSources = [...this._additionalSources];
this._additionalSources.clear();
if (!filePath) {
await this._channel.tracingStopChunk({ mode: "discard" });
if (this._stacksId)
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
return;
}
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error("Cannot save trace in thin clients");
const isLocal = !this._connection.isRemote();
if (isLocal) {
const result2 = await this._channel.tracingStopChunk({ mode: "entries" });
await localUtils.zip({ zipFile: filePath, entries: result2.entries, mode: "write", stacksId: this._stacksId, includeSources: this._includeSources, additionalSources });
return;
}
const result = await this._channel.tracingStopChunk({ mode: "archive" });
if (!result.artifact) {
if (this._stacksId)
await localUtils.traceDiscarded({ stacksId: this._stacksId });
return;
}
const artifact = import_artifact.Artifact.from(result.artifact);
await artifact.saveAs(filePath);
await artifact.delete();
await localUtils.zip({ zipFile: filePath, entries: [], mode: "append", stacksId: this._stacksId, includeSources: this._includeSources, additionalSources });
}
_resetStackCounter() {
if (this._isTracing) {
this._isTracing = false;
this._connection.setIsTracing(false);
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Tracing
});
@@ -0,0 +1,28 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var types_exports = {};
__export(types_exports, {
kLifecycleEvents: () => kLifecycleEvents
});
module.exports = __toCommonJS(types_exports);
const kLifecycleEvents = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle", "commit"]);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
kLifecycleEvents
});
@@ -0,0 +1,52 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var video_exports = {};
__export(video_exports, {
Video: () => Video
});
module.exports = __toCommonJS(video_exports);
var import_eventEmitter = require("./eventEmitter");
class Video extends import_eventEmitter.EventEmitter {
constructor(page, connection, artifact) {
super(page._platform);
this._isRemote = false;
this._isRemote = connection.isRemote();
this._artifact = artifact;
}
async path() {
if (this._isRemote)
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
if (!this._artifact)
throw new Error("Video recording has not been started.");
return this._artifact._initializer.absolutePath;
}
async saveAs(path) {
if (!this._artifact)
throw new Error("Video recording has not been started.");
return await this._artifact.saveAs(path);
}
async delete() {
if (this._artifact)
await this._artifact.delete();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Video
});
@@ -0,0 +1,142 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var waiter_exports = {};
__export(waiter_exports, {
Waiter: () => Waiter
});
module.exports = __toCommonJS(waiter_exports);
var import_errors = require("./errors");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class Waiter {
constructor(channelOwner, event) {
this._failures = [];
this._logs = [];
this._waitId = channelOwner._platform.createGuid();
this._channelOwner = channelOwner;
this._savedZone = channelOwner._platform.zones.current().pop();
this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: "before", event } }).catch(() => {
});
this._dispose = [
() => this._channelOwner._wrapApiCall(async () => {
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: "after", error: this._error } });
}, { internal: true }).catch(() => {
})
];
}
static createForEvent(channelOwner, event) {
return new Waiter(channelOwner, event);
}
async waitForEvent(emitter, event, predicate) {
const { promise, dispose } = waitForEvent(emitter, event, this._savedZone, predicate);
return await this.waitForPromise(promise, dispose);
}
rejectOnEvent(emitter, event, error, predicate) {
const { promise, dispose } = waitForEvent(emitter, event, this._savedZone, predicate);
this._rejectOn(promise.then(() => {
throw typeof error === "function" ? error() : error;
}), dispose);
}
rejectOnTimeout(timeout, message) {
if (!timeout)
return;
const { promise, dispose } = waitForTimeout(timeout);
this._rejectOn(promise.then(() => {
throw new import_errors.TimeoutError(message);
}), dispose);
}
rejectImmediately(error) {
this._immediateError = error;
}
dispose() {
for (const dispose of this._dispose)
dispose();
}
async waitForPromise(promise, dispose) {
try {
if (this._immediateError)
throw this._immediateError;
const result = await Promise.race([promise, ...this._failures]);
if (dispose)
dispose();
return result;
} catch (e) {
if (dispose)
dispose();
this._error = e.message;
this.dispose();
(0, import_stackTrace.rewriteErrorMessage)(e, e.message + formatLogRecording(this._logs));
throw e;
}
}
log(s) {
this._logs.push(s);
this._channelOwner._wrapApiCall(async () => {
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: "log", message: s } });
}, { internal: true }).catch(() => {
});
}
_rejectOn(promise, dispose) {
this._failures.push(promise);
if (dispose)
this._dispose.push(dispose);
}
}
function waitForEvent(emitter, event, savedZone, predicate) {
let listener;
const promise = new Promise((resolve, reject) => {
listener = async (eventArg) => {
await savedZone.run(async () => {
try {
if (predicate && !await predicate(eventArg))
return;
emitter.removeListener(event, listener);
resolve(eventArg);
} catch (e) {
emitter.removeListener(event, listener);
reject(e);
}
});
};
emitter.addListener(event, listener);
});
const dispose = () => emitter.removeListener(event, listener);
return { promise, dispose };
}
function waitForTimeout(timeout) {
let timeoutId;
const promise = new Promise((resolve) => timeoutId = setTimeout(resolve, timeout));
const dispose = () => clearTimeout(timeoutId);
return { promise, dispose };
}
function formatLogRecording(log) {
if (!log.length)
return "";
const header = ` logs `;
const headerLength = 60;
const leftLength = (headerLength - header.length) / 2;
const rightLength = headerLength - header.length - leftLength;
return `
${"=".repeat(leftLength)}${header}${"=".repeat(rightLength)}
${log.join("\n")}
${"=".repeat(headerLength)}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Waiter
});
@@ -0,0 +1,39 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var webError_exports = {};
__export(webError_exports, {
WebError: () => WebError
});
module.exports = __toCommonJS(webError_exports);
class WebError {
constructor(page, error) {
this._page = page;
this._error = error;
}
page() {
return this._page;
}
error() {
return this._error;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
WebError
});
@@ -0,0 +1,85 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var worker_exports = {};
__export(worker_exports, {
Worker: () => Worker
});
module.exports = __toCommonJS(worker_exports);
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_jsHandle = require("./jsHandle");
var import_manualPromise = require("../utils/isomorphic/manualPromise");
var import_timeoutSettings = require("./timeoutSettings");
var import_waiter = require("./waiter");
class Worker extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
// Set for service workers.
this._closedScope = new import_manualPromise.LongStandingScope();
this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
[import_events.Events.Worker.Console, "console"]
]));
this._channel.on("close", () => {
if (this._page)
this._page._workers.delete(this);
if (this._context)
this._context._serviceWorkers.delete(this);
this.emit(import_events.Events.Worker.Close, this);
});
this.once(import_events.Events.Worker.Close, () => this._closedScope.close(this._page?._closeErrorWithReason() || new import_errors.TargetClosedError()));
}
static fromNullable(worker) {
return worker ? Worker.from(worker) : null;
}
static from(worker) {
return worker._object;
}
url() {
return this._initializer.url;
}
async evaluate(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async evaluateHandle(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return import_jsHandle.JSHandle.from(result.handle);
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeoutSettings = this._page?._timeoutSettings ?? this._context?._timeoutSettings ?? new import_timeoutSettings.TimeoutSettings(this._platform);
const timeout = timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.Worker.Close)
waiter.rejectOnEvent(this, import_events.Events.Worker.Close, () => new import_errors.TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Worker
});
@@ -0,0 +1,39 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var writableStream_exports = {};
__export(writableStream_exports, {
WritableStream: () => WritableStream
});
module.exports = __toCommonJS(writableStream_exports);
var import_channelOwner = require("./channelOwner");
class WritableStream extends import_channelOwner.ChannelOwner {
static from(Stream) {
return Stream._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
stream() {
return this._platform.streamWritable(this._channel);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
WritableStream
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,336 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var webSocketMockSource_exports = {};
__export(webSocketMockSource_exports, {
source: () => source
});
module.exports = __toCommonJS(webSocketMockSource_exports);
const source = `
var __commonJS = obj => {
let required = false;
let result;
return function __require() {
if (!required) {
required = true;
let fn;
for (const name in obj) { fn = obj[name]; break; }
const module = { exports: {} };
fn(module.exports, module);
result = module.exports;
}
return result;
}
};
var __export = (target, all) => {for (var name in all) target[name] = all[name];};
var __toESM = mod => ({ ...mod, 'default': mod });
var __toCommonJS = mod => ({ ...mod, __esModule: true });
// packages/injected/src/webSocketMock.ts
var webSocketMock_exports = {};
__export(webSocketMock_exports, {
inject: () => inject
});
module.exports = __toCommonJS(webSocketMock_exports);
function inject(globalThis) {
if (globalThis.__pwWebSocketDispatch)
return;
function generateId() {
const bytes = new Uint8Array(32);
globalThis.crypto.getRandomValues(bytes);
const hex = "0123456789abcdef";
return [...bytes].map((value) => {
const high = Math.floor(value / 16);
const low = value % 16;
return hex[high] + hex[low];
}).join("");
}
function bufferToData(b) {
let s = "";
for (let i = 0; i < b.length; i++)
s += String.fromCharCode(b[i]);
return { data: globalThis.btoa(s), isBase64: true };
}
function stringToBuffer(s) {
s = globalThis.atob(s);
const b = new Uint8Array(s.length);
for (let i = 0; i < s.length; i++)
b[i] = s.charCodeAt(i);
return b.buffer;
}
function messageToData(message, cb) {
if (message instanceof globalThis.Blob)
return message.arrayBuffer().then((buffer) => cb(bufferToData(new Uint8Array(buffer))));
if (typeof message === "string")
return cb({ data: message, isBase64: false });
if (ArrayBuffer.isView(message))
return cb(bufferToData(new Uint8Array(message.buffer, message.byteOffset, message.byteLength)));
return cb(bufferToData(new Uint8Array(message)));
}
function dataToMessage(data, binaryType) {
if (!data.isBase64)
return data.data;
const buffer = stringToBuffer(data.data);
return binaryType === "arraybuffer" ? buffer : new Blob([buffer]);
}
const binding = globalThis.__pwWebSocketBinding;
const NativeWebSocket = globalThis.WebSocket;
const idToWebSocket = /* @__PURE__ */ new Map();
globalThis.__pwWebSocketDispatch = (request) => {
const ws = idToWebSocket.get(request.id);
if (!ws)
return;
if (request.type === "connect")
ws._apiConnect();
if (request.type === "passthrough")
ws._apiPassThrough();
if (request.type === "ensureOpened")
ws._apiEnsureOpened();
if (request.type === "sendToPage")
ws._apiSendToPage(dataToMessage(request.data, ws.binaryType));
if (request.type === "closePage")
ws._apiClosePage(request.code, request.reason, request.wasClean);
if (request.type === "sendToServer")
ws._apiSendToServer(dataToMessage(request.data, ws.binaryType));
if (request.type === "closeServer")
ws._apiCloseServer(request.code, request.reason, request.wasClean);
};
const _WebSocketMock = class _WebSocketMock extends EventTarget {
constructor(url, protocols) {
var _a, _b;
super();
// WebSocket.CLOSED
this.CONNECTING = 0;
// WebSocket.CONNECTING
this.OPEN = 1;
// WebSocket.OPEN
this.CLOSING = 2;
// WebSocket.CLOSING
this.CLOSED = 3;
// WebSocket.CLOSED
this._oncloseListener = null;
this._onerrorListener = null;
this._onmessageListener = null;
this._onopenListener = null;
this.bufferedAmount = 0;
this.extensions = "";
this.protocol = "";
this.readyState = 0;
this._origin = "";
this._passthrough = false;
this._wsBufferedMessages = [];
this._binaryType = "blob";
this.url = new URL(url, globalThis.window.document.baseURI).href.replace(/^http/, "ws");
this._origin = (_b = (_a = URL.parse(this.url)) == null ? void 0 : _a.origin) != null ? _b : "";
this._protocols = protocols;
this._id = generateId();
idToWebSocket.set(this._id, this);
binding({ type: "onCreate", id: this._id, url: this.url });
}
// --- native WebSocket implementation ---
get binaryType() {
return this._binaryType;
}
set binaryType(type) {
this._binaryType = type;
if (this._ws)
this._ws.binaryType = type;
}
get onclose() {
return this._oncloseListener;
}
set onclose(listener) {
if (this._oncloseListener)
this.removeEventListener("close", this._oncloseListener);
this._oncloseListener = listener;
if (this._oncloseListener)
this.addEventListener("close", this._oncloseListener);
}
get onerror() {
return this._onerrorListener;
}
set onerror(listener) {
if (this._onerrorListener)
this.removeEventListener("error", this._onerrorListener);
this._onerrorListener = listener;
if (this._onerrorListener)
this.addEventListener("error", this._onerrorListener);
}
get onopen() {
return this._onopenListener;
}
set onopen(listener) {
if (this._onopenListener)
this.removeEventListener("open", this._onopenListener);
this._onopenListener = listener;
if (this._onopenListener)
this.addEventListener("open", this._onopenListener);
}
get onmessage() {
return this._onmessageListener;
}
set onmessage(listener) {
if (this._onmessageListener)
this.removeEventListener("message", this._onmessageListener);
this._onmessageListener = listener;
if (this._onmessageListener)
this.addEventListener("message", this._onmessageListener);
}
send(message) {
if (this.readyState === _WebSocketMock.CONNECTING)
throw new DOMException(\`Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.\`);
if (this.readyState !== _WebSocketMock.OPEN)
throw new DOMException(\`WebSocket is already in CLOSING or CLOSED state.\`);
if (this._passthrough) {
if (this._ws)
this._apiSendToServer(message);
} else {
messageToData(message, (data) => binding({ type: "onMessageFromPage", id: this._id, data }));
}
}
close(code, reason) {
if (code !== void 0 && code !== 1e3 && (code < 3e3 || code > 4999))
throw new DOMException(\`Failed to execute 'close' on 'WebSocket': The close code must be either 1000, or between 3000 and 4999. \${code} is neither.\`);
if (this.readyState === _WebSocketMock.OPEN || this.readyState === _WebSocketMock.CONNECTING)
this.readyState = _WebSocketMock.CLOSING;
if (this._passthrough)
this._apiCloseServer(code, reason, true);
else
binding({ type: "onClosePage", id: this._id, code, reason, wasClean: true });
}
// --- methods called from the routing API ---
_apiEnsureOpened() {
if (!this._ws)
this._ensureOpened();
}
_apiSendToPage(message) {
this._ensureOpened();
if (this.readyState !== _WebSocketMock.OPEN)
throw new DOMException(\`WebSocket is already in CLOSING or CLOSED state.\`);
this.dispatchEvent(new MessageEvent("message", { data: message, origin: this._origin, cancelable: true }));
}
_apiSendToServer(message) {
if (!this._ws)
throw new Error("Cannot send a message before connecting to the server");
if (this._ws.readyState === _WebSocketMock.CONNECTING)
this._wsBufferedMessages.push(message);
else
this._ws.send(message);
}
_apiConnect() {
if (this._ws)
throw new Error("Can only connect to the server once");
this._ws = new NativeWebSocket(this.url, this._protocols);
this._ws.binaryType = this._binaryType;
this._ws.onopen = () => {
for (const message of this._wsBufferedMessages)
this._ws.send(message);
this._wsBufferedMessages = [];
this._ensureOpened();
};
this._ws.onclose = (event) => {
this._onWSClose(event.code, event.reason, event.wasClean);
};
this._ws.onmessage = (event) => {
if (this._passthrough)
this._apiSendToPage(event.data);
else
messageToData(event.data, (data) => binding({ type: "onMessageFromServer", id: this._id, data }));
};
this._ws.onerror = () => {
const event = new Event("error", { cancelable: true });
this.dispatchEvent(event);
};
}
// This method connects to the server, and passes all messages through,
// as if WebSocketMock was not engaged.
_apiPassThrough() {
this._passthrough = true;
this._apiConnect();
}
_apiCloseServer(code, reason, wasClean) {
if (!this._ws) {
this._onWSClose(code, reason, wasClean);
return;
}
if (this._ws.readyState === _WebSocketMock.CONNECTING || this._ws.readyState === _WebSocketMock.OPEN)
this._ws.close(code, reason);
}
_apiClosePage(code, reason, wasClean) {
if (this.readyState === _WebSocketMock.CLOSED)
return;
this.readyState = _WebSocketMock.CLOSED;
this.dispatchEvent(new CloseEvent("close", { code, reason, wasClean, cancelable: true }));
this._maybeCleanup();
if (this._passthrough)
this._apiCloseServer(code, reason, wasClean);
else
binding({ type: "onClosePage", id: this._id, code, reason, wasClean });
}
// --- internals ---
_ensureOpened() {
var _a;
if (this.readyState !== _WebSocketMock.CONNECTING)
return;
this.extensions = ((_a = this._ws) == null ? void 0 : _a.extensions) || "";
if (this._ws)
this.protocol = this._ws.protocol;
else if (Array.isArray(this._protocols))
this.protocol = this._protocols[0] || "";
else
this.protocol = this._protocols || "";
this.readyState = _WebSocketMock.OPEN;
this.dispatchEvent(new Event("open", { cancelable: true }));
}
_onWSClose(code, reason, wasClean) {
if (this._passthrough)
this._apiClosePage(code, reason, wasClean);
else
binding({ type: "onCloseServer", id: this._id, code, reason, wasClean });
if (this._ws) {
this._ws.onopen = null;
this._ws.onclose = null;
this._ws.onmessage = null;
this._ws.onerror = null;
this._ws = void 0;
this._wsBufferedMessages = [];
}
this._maybeCleanup();
}
_maybeCleanup() {
if (this.readyState === _WebSocketMock.CLOSED && !this._ws)
idToWebSocket.delete(this._id);
}
};
_WebSocketMock.CONNECTING = 0;
// WebSocket.CONNECTING
_WebSocketMock.OPEN = 1;
// WebSocket.OPEN
_WebSocketMock.CLOSING = 2;
// WebSocket.CLOSING
_WebSocketMock.CLOSED = 3;
let WebSocketMock = _WebSocketMock;
globalThis.WebSocket = class WebSocket extends WebSocketMock {
};
}
`;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
source
});
@@ -0,0 +1,60 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var inProcessFactory_exports = {};
__export(inProcessFactory_exports, {
createInProcessPlaywright: () => createInProcessPlaywright
});
module.exports = __toCommonJS(inProcessFactory_exports);
var import_androidServerImpl = require("./androidServerImpl");
var import_browserServerImpl = require("./browserServerImpl");
var import_server = require("./server");
var import_nodePlatform = require("./server/utils/nodePlatform");
var import_connection = require("./client/connection");
function createInProcessPlaywright() {
const playwright = (0, import_server.createPlaywright)({ sdkLanguage: process.env.PW_LANG_NAME || "javascript" });
const clientConnection = new import_connection.Connection(import_nodePlatform.nodePlatform);
clientConnection.useRawBuffers();
const dispatcherConnection = new import_server.DispatcherConnection(
true
/* local */
);
dispatcherConnection.onmessage = (message) => clientConnection.dispatch(message);
clientConnection.onmessage = (message) => dispatcherConnection.dispatch(message);
const rootScope = new import_server.RootDispatcher(dispatcherConnection);
new import_server.PlaywrightDispatcher(rootScope, playwright);
const playwrightAPI = clientConnection.getObjectWithKnownName("Playwright");
playwrightAPI.chromium._serverLauncher = new import_browserServerImpl.BrowserServerLauncherImpl("chromium");
playwrightAPI.firefox._serverLauncher = new import_browserServerImpl.BrowserServerLauncherImpl("firefox");
playwrightAPI.webkit._serverLauncher = new import_browserServerImpl.BrowserServerLauncherImpl("webkit");
playwrightAPI._android._serverLauncher = new import_androidServerImpl.AndroidServerLauncherImpl();
dispatcherConnection.onmessage = (message) => setImmediate(() => clientConnection.dispatch(message));
clientConnection.onmessage = (message) => setImmediate(() => dispatcherConnection.dispatch(message));
clientConnection.toImpl = (x) => {
if (x instanceof import_connection.Connection)
return x === clientConnection ? dispatcherConnection : void 0;
if (!x)
return dispatcherConnection._dispatcherByGuid.get("");
return dispatcherConnection._dispatcherByGuid.get(x._guid)._object;
};
return playwrightAPI;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createInProcessPlaywright
});
@@ -0,0 +1,3 @@
"use strict";
var import_inProcessFactory = require("./inProcessFactory");
module.exports = (0, import_inProcessFactory.createInProcessPlaywright)();
@@ -0,0 +1,78 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var mcpBundle_exports = {};
__export(mcpBundle_exports, {
CallToolRequestSchema: () => CallToolRequestSchema,
Client: () => Client,
ListRootsRequestSchema: () => ListRootsRequestSchema,
ListToolsRequestSchema: () => ListToolsRequestSchema,
PingRequestSchema: () => PingRequestSchema,
ProgressNotificationSchema: () => ProgressNotificationSchema,
SSEClientTransport: () => SSEClientTransport,
SSEServerTransport: () => SSEServerTransport,
Server: () => Server,
StdioClientTransport: () => StdioClientTransport,
StdioServerTransport: () => StdioServerTransport,
StreamableHTTPClientTransport: () => StreamableHTTPClientTransport,
StreamableHTTPServerTransport: () => StreamableHTTPServerTransport,
zodToJsonSchema: () => zodToJsonSchema
});
module.exports = __toCommonJS(mcpBundle_exports);
var bundle = __toESM(require("./mcpBundleImpl"));
const zodToJsonSchema = bundle.zodToJsonSchema;
const Client = bundle.Client;
const Server = bundle.Server;
const SSEClientTransport = bundle.SSEClientTransport;
const SSEServerTransport = bundle.SSEServerTransport;
const StdioClientTransport = bundle.StdioClientTransport;
const StdioServerTransport = bundle.StdioServerTransport;
const StreamableHTTPServerTransport = bundle.StreamableHTTPServerTransport;
const StreamableHTTPClientTransport = bundle.StreamableHTTPClientTransport;
const CallToolRequestSchema = bundle.CallToolRequestSchema;
const ListRootsRequestSchema = bundle.ListRootsRequestSchema;
const ProgressNotificationSchema = bundle.ProgressNotificationSchema;
const ListToolsRequestSchema = bundle.ListToolsRequestSchema;
const PingRequestSchema = bundle.PingRequestSchema;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CallToolRequestSchema,
Client,
ListRootsRequestSchema,
ListToolsRequestSchema,
PingRequestSchema,
ProgressNotificationSchema,
SSEClientTransport,
SSEServerTransport,
Server,
StdioClientTransport,
StdioServerTransport,
StreamableHTTPClientTransport,
StreamableHTTPServerTransport,
zodToJsonSchema
});
File diff suppressed because one or more lines are too long
@@ -0,0 +1,76 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var outofprocess_exports = {};
__export(outofprocess_exports, {
start: () => start
});
module.exports = __toCommonJS(outofprocess_exports);
var childProcess = __toESM(require("child_process"));
var import_path = __toESM(require("path"));
var import_connection = require("./client/connection");
var import_pipeTransport = require("./server/utils/pipeTransport");
var import_manualPromise = require("./utils/isomorphic/manualPromise");
var import_nodePlatform = require("./server/utils/nodePlatform");
async function start(env = {}) {
const client = new PlaywrightClient(env);
const playwright = await client._playwright;
playwright.driverProcess = client._driverProcess;
return { playwright, stop: () => client.stop() };
}
class PlaywrightClient {
constructor(env) {
this._closePromise = new import_manualPromise.ManualPromise();
this._driverProcess = childProcess.fork(import_path.default.join(__dirname, "..", "cli.js"), ["run-driver"], {
stdio: "pipe",
detached: true,
env: {
...process.env,
...env
}
});
this._driverProcess.unref();
this._driverProcess.stderr.on("data", (data) => process.stderr.write(data));
const connection = new import_connection.Connection(import_nodePlatform.nodePlatform);
const transport = new import_pipeTransport.PipeTransport(this._driverProcess.stdin, this._driverProcess.stdout);
connection.onmessage = (message) => transport.send(JSON.stringify(message));
transport.onmessage = (message) => connection.dispatch(JSON.parse(message));
transport.onclose = () => this._closePromise.resolve();
this._playwright = connection.initializePlaywright();
}
async stop() {
this._driverProcess.stdin.destroy();
this._driverProcess.stdout.destroy();
this._driverProcess.stderr.destroy();
await this._closePromise;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
start
});
@@ -0,0 +1,197 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var serializers_exports = {};
__export(serializers_exports, {
parseSerializedValue: () => parseSerializedValue,
serializePlainValue: () => serializePlainValue,
serializeValue: () => serializeValue
});
module.exports = __toCommonJS(serializers_exports);
function parseSerializedValue(value, handles) {
return innerParseSerializedValue(value, handles, /* @__PURE__ */ new Map(), []);
}
function innerParseSerializedValue(value, handles, refs, accessChain) {
if (value.ref !== void 0)
return refs.get(value.ref);
if (value.n !== void 0)
return value.n;
if (value.s !== void 0)
return value.s;
if (value.b !== void 0)
return value.b;
if (value.v !== void 0) {
if (value.v === "undefined")
return void 0;
if (value.v === "null")
return null;
if (value.v === "NaN")
return NaN;
if (value.v === "Infinity")
return Infinity;
if (value.v === "-Infinity")
return -Infinity;
if (value.v === "-0")
return -0;
}
if (value.d !== void 0)
return new Date(value.d);
if (value.u !== void 0)
return new URL(value.u);
if (value.bi !== void 0)
return BigInt(value.bi);
if (value.e !== void 0) {
const error = new Error(value.e.m);
error.name = value.e.n;
error.stack = value.e.s;
return error;
}
if (value.r !== void 0)
return new RegExp(value.r.p, value.r.f);
if (value.ta !== void 0) {
const ctor = typedArrayKindToConstructor[value.ta.k];
return new ctor(value.ta.b.buffer, value.ta.b.byteOffset, value.ta.b.length / ctor.BYTES_PER_ELEMENT);
}
if (value.a !== void 0) {
const result = [];
refs.set(value.id, result);
for (let i = 0; i < value.a.length; i++)
result.push(innerParseSerializedValue(value.a[i], handles, refs, [...accessChain, i]));
return result;
}
if (value.o !== void 0) {
const result = {};
refs.set(value.id, result);
for (const { k, v } of value.o)
result[k] = innerParseSerializedValue(v, handles, refs, [...accessChain, k]);
return result;
}
if (value.h !== void 0) {
if (handles === void 0)
throw new Error("Unexpected handle");
return handles[value.h];
}
throw new Error(`Attempting to deserialize unexpected value${accessChainToDisplayString(accessChain)}: ${value}`);
}
function serializeValue(value, handleSerializer) {
return innerSerializeValue(value, handleSerializer, { lastId: 0, visited: /* @__PURE__ */ new Map() }, []);
}
function serializePlainValue(arg) {
return serializeValue(arg, (value) => ({ fallThrough: value }));
}
function innerSerializeValue(value, handleSerializer, visitorInfo, accessChain) {
const handle = handleSerializer(value);
if ("fallThrough" in handle)
value = handle.fallThrough;
else
return handle;
if (typeof value === "symbol")
return { v: "undefined" };
if (Object.is(value, void 0))
return { v: "undefined" };
if (Object.is(value, null))
return { v: "null" };
if (Object.is(value, NaN))
return { v: "NaN" };
if (Object.is(value, Infinity))
return { v: "Infinity" };
if (Object.is(value, -Infinity))
return { v: "-Infinity" };
if (Object.is(value, -0))
return { v: "-0" };
if (typeof value === "boolean")
return { b: value };
if (typeof value === "number")
return { n: value };
if (typeof value === "string")
return { s: value };
if (typeof value === "bigint")
return { bi: value.toString() };
if (isError(value))
return { e: { n: value.name, m: value.message, s: value.stack || "" } };
if (isDate(value))
return { d: value.toJSON() };
if (isURL(value))
return { u: value.toJSON() };
if (isRegExp(value))
return { r: { p: value.source, f: value.flags } };
const typedArrayKind = constructorToTypedArrayKind.get(value.constructor);
if (typedArrayKind)
return { ta: { b: Buffer.from(value.buffer, value.byteOffset, value.byteLength), k: typedArrayKind } };
const id = visitorInfo.visited.get(value);
if (id)
return { ref: id };
if (Array.isArray(value)) {
const a = [];
const id2 = ++visitorInfo.lastId;
visitorInfo.visited.set(value, id2);
for (let i = 0; i < value.length; ++i)
a.push(innerSerializeValue(value[i], handleSerializer, visitorInfo, [...accessChain, i]));
return { a, id: id2 };
}
if (typeof value === "object") {
const o = [];
const id2 = ++visitorInfo.lastId;
visitorInfo.visited.set(value, id2);
for (const name of Object.keys(value))
o.push({ k: name, v: innerSerializeValue(value[name], handleSerializer, visitorInfo, [...accessChain, name]) });
return { o, id: id2 };
}
throw new Error(`Attempting to serialize unexpected value${accessChainToDisplayString(accessChain)}: ${value}`);
}
function accessChainToDisplayString(accessChain) {
const chainString = accessChain.map((accessor, i) => {
if (typeof accessor === "string")
return i ? `.${accessor}` : accessor;
return `[${accessor}]`;
}).join("");
return chainString.length > 0 ? ` at position "${chainString}"` : "";
}
function isRegExp(obj) {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === "[object RegExp]";
}
function isDate(obj) {
return obj instanceof Date || Object.prototype.toString.call(obj) === "[object Date]";
}
function isURL(obj) {
return obj instanceof URL || Object.prototype.toString.call(obj) === "[object URL]";
}
function isError(obj) {
const proto = obj ? Object.getPrototypeOf(obj) : null;
return obj instanceof Error || proto?.name === "Error" || proto && isError(proto);
}
const typedArrayKindToConstructor = {
i8: Int8Array,
ui8: Uint8Array,
ui8c: Uint8ClampedArray,
i16: Int16Array,
ui16: Uint16Array,
i32: Int32Array,
ui32: Uint32Array,
f32: Float32Array,
f64: Float64Array,
bi64: BigInt64Array,
bui64: BigUint64Array
};
const constructorToTypedArrayKind = new Map(Object.entries(typedArrayKindToConstructor).map(([k, v]) => [v, k]));
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
parseSerializedValue,
serializePlainValue,
serializeValue
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,193 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var validatorPrimitives_exports = {};
__export(validatorPrimitives_exports, {
ValidationError: () => ValidationError,
createMetadataValidator: () => createMetadataValidator,
findValidator: () => findValidator,
maybeFindValidator: () => maybeFindValidator,
scheme: () => scheme,
tAny: () => tAny,
tArray: () => tArray,
tBinary: () => tBinary,
tBoolean: () => tBoolean,
tChannel: () => tChannel,
tEnum: () => tEnum,
tFloat: () => tFloat,
tInt: () => tInt,
tObject: () => tObject,
tOptional: () => tOptional,
tString: () => tString,
tType: () => tType,
tUndefined: () => tUndefined
});
module.exports = __toCommonJS(validatorPrimitives_exports);
class ValidationError extends Error {
}
const scheme = {};
function findValidator(type, method, kind) {
const validator = maybeFindValidator(type, method, kind);
if (!validator)
throw new ValidationError(`Unknown scheme for ${kind}: ${type}.${method}`);
return validator;
}
function maybeFindValidator(type, method, kind) {
const schemeName = type + (kind === "Initializer" ? "" : method[0].toUpperCase() + method.substring(1)) + kind;
return scheme[schemeName];
}
function createMetadataValidator() {
return tOptional(scheme["Metadata"]);
}
const tFloat = (arg, path, context) => {
if (arg instanceof Number)
return arg.valueOf();
if (typeof arg === "number")
return arg;
throw new ValidationError(`${path}: expected float, got ${typeof arg}`);
};
const tInt = (arg, path, context) => {
let value;
if (arg instanceof Number)
value = arg.valueOf();
else if (typeof arg === "number")
value = arg;
else
throw new ValidationError(`${path}: expected integer, got ${typeof arg}`);
if (!Number.isInteger(value))
throw new ValidationError(`${path}: expected integer, got float ${value}`);
return value;
};
const tBoolean = (arg, path, context) => {
if (arg instanceof Boolean)
return arg.valueOf();
if (typeof arg === "boolean")
return arg;
throw new ValidationError(`${path}: expected boolean, got ${typeof arg}`);
};
const tString = (arg, path, context) => {
if (arg instanceof String)
return arg.valueOf();
if (typeof arg === "string")
return arg;
throw new ValidationError(`${path}: expected string, got ${typeof arg}`);
};
const tBinary = (arg, path, context) => {
if (context.binary === "fromBase64") {
if (arg instanceof String)
return Buffer.from(arg.valueOf(), "base64");
if (typeof arg === "string")
return Buffer.from(arg, "base64");
throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`);
}
if (context.binary === "toBase64") {
if (!(arg instanceof Buffer))
throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
return arg.toString("base64");
}
if (context.binary === "buffer") {
if (!(arg instanceof Buffer) && !(arg instanceof Object))
throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
return arg;
}
throw new ValidationError(`Unsupported binary behavior "${context.binary}"`);
};
const tUndefined = (arg, path, context) => {
if (Object.is(arg, void 0))
return arg;
throw new ValidationError(`${path}: expected undefined, got ${typeof arg}`);
};
const tAny = (arg, path, context) => {
return arg;
};
const tOptional = (v) => {
return (arg, path, context) => {
if (Object.is(arg, void 0))
return arg;
return v(arg, path, context);
};
};
const tArray = (v) => {
return (arg, path, context) => {
if (!Array.isArray(arg))
throw new ValidationError(`${path}: expected array, got ${typeof arg}`);
return arg.map((x, index) => v(x, path + "[" + index + "]", context));
};
};
const tObject = (s) => {
return (arg, path, context) => {
if (Object.is(arg, null))
throw new ValidationError(`${path}: expected object, got null`);
if (typeof arg !== "object")
throw new ValidationError(`${path}: expected object, got ${typeof arg}`);
const result = {};
for (const [key, v] of Object.entries(s)) {
const value = v(arg[key], path ? path + "." + key : key, context);
if (!Object.is(value, void 0))
result[key] = value;
}
if (context.isUnderTest()) {
for (const [key, value] of Object.entries(arg)) {
if (key.startsWith("__testHook"))
result[key] = value;
}
}
return result;
};
};
const tEnum = (e) => {
return (arg, path, context) => {
if (!e.includes(arg))
throw new ValidationError(`${path}: expected one of (${e.join("|")})`);
return arg;
};
};
const tChannel = (names) => {
return (arg, path, context) => {
return context.tChannelImpl(names, arg, path, context);
};
};
const tType = (name) => {
return (arg, path, context) => {
const v = scheme[name];
if (!v)
throw new ValidationError(path + ': unknown type "' + name + '"');
return v(arg, path, context);
};
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ValidationError,
createMetadataValidator,
findValidator,
maybeFindValidator,
scheme,
tAny,
tArray,
tBinary,
tBoolean,
tChannel,
tEnum,
tFloat,
tInt,
tObject,
tOptional,
tString,
tType,
tUndefined
});
@@ -0,0 +1,131 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var playwrightConnection_exports = {};
__export(playwrightConnection_exports, {
PlaywrightConnection: () => PlaywrightConnection
});
module.exports = __toCommonJS(playwrightConnection_exports);
var import_server = require("../server");
var import_android = require("../server/android/android");
var import_browser = require("../server/browser");
var import_debugControllerDispatcher = require("../server/dispatchers/debugControllerDispatcher");
var import_profiler = require("../server/utils/profiler");
var import_utils = require("../utils");
var import_debugLogger = require("../server/utils/debugLogger");
class PlaywrightConnection {
constructor(semaphore, transport, controller, playwright, initialize, id) {
this._cleanups = [];
this._disconnected = false;
this._transport = transport;
this._semaphore = semaphore;
this._id = id;
this._profileName = (/* @__PURE__ */ new Date()).toISOString();
const lock = this._semaphore.acquire();
this._dispatcherConnection = new import_server.DispatcherConnection();
this._dispatcherConnection.onmessage = async (message) => {
await lock;
if (!transport.isClosed()) {
const messageString = JSON.stringify(message);
if (import_debugLogger.debugLogger.isEnabled("server:channel"))
import_debugLogger.debugLogger.log("server:channel", `[${this._id}] ${(0, import_utils.monotonicTime)() * 1e3} SEND \u25BA ${messageString}`);
if (import_debugLogger.debugLogger.isEnabled("server:metadata"))
this.logServerMetadata(message, messageString, "SEND");
transport.send(messageString);
}
};
transport.on("message", async (message) => {
await lock;
const messageString = Buffer.from(message).toString();
const jsonMessage = JSON.parse(messageString);
if (import_debugLogger.debugLogger.isEnabled("server:channel"))
import_debugLogger.debugLogger.log("server:channel", `[${this._id}] ${(0, import_utils.monotonicTime)() * 1e3} \u25C0 RECV ${messageString}`);
if (import_debugLogger.debugLogger.isEnabled("server:metadata"))
this.logServerMetadata(jsonMessage, messageString, "RECV");
this._dispatcherConnection.dispatch(jsonMessage);
});
transport.on("close", () => this._onDisconnect());
transport.on("error", (error) => this._onDisconnect(error));
if (controller) {
import_debugLogger.debugLogger.log("server", `[${this._id}] engaged reuse controller mode`);
this._root = new import_debugControllerDispatcher.DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController);
return;
}
this._root = new import_server.RootDispatcher(this._dispatcherConnection, async (scope, params) => {
await (0, import_profiler.startProfiling)();
const options = await initialize();
if (options.preLaunchedBrowser) {
const browser = options.preLaunchedBrowser;
browser.options.sdkLanguage = params.sdkLanguage;
browser.on(import_browser.Browser.Events.Disconnected, () => {
this.close({ code: 1001, reason: "Browser closed" });
});
}
if (options.preLaunchedAndroidDevice) {
const androidDevice = options.preLaunchedAndroidDevice;
androidDevice.on(import_android.AndroidDevice.Events.Close, () => {
this.close({ code: 1001, reason: "Android device disconnected" });
});
}
if (options.dispose)
this._cleanups.push(options.dispose);
const dispatcher = new import_server.PlaywrightDispatcher(scope, playwright, options);
this._cleanups.push(() => dispatcher.cleanup());
return dispatcher;
});
}
async _onDisconnect(error) {
if (this._disconnected)
return;
this._disconnected = true;
import_debugLogger.debugLogger.log("server", `[${this._id}] disconnected. error: ${error}`);
await this._root.stopPendingOperations(new Error("Disconnected")).catch(() => {
});
this._root._dispose();
import_debugLogger.debugLogger.log("server", `[${this._id}] starting cleanup`);
for (const cleanup of this._cleanups)
await cleanup().catch(() => {
});
await (0, import_profiler.stopProfiling)(this._profileName);
this._semaphore.release();
import_debugLogger.debugLogger.log("server", `[${this._id}] finished cleanup`);
}
logServerMetadata(message, messageString, direction) {
const serverLogMetadata = {
wallTime: Date.now(),
id: message.id,
guid: message.guid,
method: message.method,
payloadSizeInBytes: Buffer.byteLength(messageString, "utf-8")
};
import_debugLogger.debugLogger.log("server:metadata", (direction === "SEND" ? "SEND \u25BA " : "\u25C0 RECV ") + JSON.stringify(serverLogMetadata));
}
async close(reason) {
if (this._disconnected)
return;
import_debugLogger.debugLogger.log("server", `[${this._id}] force closing connection: ${reason?.reason || ""} (${reason?.code || 0})`);
try {
this._transport.close(reason);
} catch (e) {
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PlaywrightConnection
});
@@ -0,0 +1,100 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var playwrightPipeServer_exports = {};
__export(playwrightPipeServer_exports, {
PlaywrightPipeServer: () => PlaywrightPipeServer
});
module.exports = __toCommonJS(playwrightPipeServer_exports);
var import_net = __toESM(require("net"));
var import_fs = __toESM(require("fs"));
var import_playwrightConnection = require("./playwrightConnection");
var import_serverTransport = require("./serverTransport");
var import_debugLogger = require("../server/utils/debugLogger");
var import_browser = require("../server/browser");
var import_utils = require("../utils");
class PlaywrightPipeServer {
constructor(browser) {
this._connections = /* @__PURE__ */ new Set();
this._connectionId = 0;
this._browser = browser;
browser.on(import_browser.Browser.Events.Disconnected, () => this.close());
}
async listen(pipeName) {
if (!pipeName.startsWith("\\\\.\\pipe\\")) {
try {
import_fs.default.unlinkSync(pipeName);
} catch {
}
}
this._server = import_net.default.createServer((socket) => {
const id = String(++this._connectionId);
import_debugLogger.debugLogger.log("server", `[${id}] pipe client connected`);
const transport = new import_serverTransport.SocketServerTransport(socket);
const connection = new import_playwrightConnection.PlaywrightConnection(
new import_utils.Semaphore(1),
transport,
false,
this._browser.attribution.playwright,
() => this._initPreLaunchedBrowserMode(id),
id
);
this._connections.add(connection);
transport.on("close", () => this._connections.delete(connection));
});
(0, import_utils.decorateServer)(this._server);
await new Promise((resolve, reject) => {
this._server.listen(pipeName, () => resolve());
this._server.on("error", reject);
});
import_debugLogger.debugLogger.log("server", `Pipe server listening at ${pipeName}`);
}
async _initPreLaunchedBrowserMode(id) {
import_debugLogger.debugLogger.log("server", `[${id}] engaged pre-launched (browser) pipe mode`);
return {
preLaunchedBrowser: this._browser,
sharedBrowser: true,
denyLaunch: true
};
}
async close() {
if (!this._server)
return;
import_debugLogger.debugLogger.log("server", "closing pipe server");
for (const connection of this._connections)
await connection.close({ code: 1001, reason: "Server closing" });
this._connections.clear();
await new Promise((f) => this._server.close(() => f()));
this._server = void 0;
import_debugLogger.debugLogger.log("server", "closed pipe server");
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PlaywrightPipeServer
});
@@ -0,0 +1,339 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var playwrightServer_exports = {};
__export(playwrightServer_exports, {
PlaywrightServer: () => PlaywrightServer
});
module.exports = __toCommonJS(playwrightServer_exports);
var import_playwrightConnection = require("./playwrightConnection");
var import_serverTransport = require("./serverTransport");
var import_playwright = require("../server/playwright");
var import_semaphore = require("../utils/isomorphic/semaphore");
var import_time = require("../utils/isomorphic/time");
var import_wsServer = require("../server/utils/wsServer");
var import_ascii = require("../server/utils/ascii");
var import_userAgent = require("../server/utils/userAgent");
var import_utils = require("../utils");
var import_socksProxy = require("../server/utils/socksProxy");
var import_browser = require("../server/browser");
var import_progress = require("../server/progress");
class PlaywrightServer {
constructor(options) {
this._dontReuseBrowsers = /* @__PURE__ */ new Set();
this._options = options;
if (options.preLaunchedBrowser) {
this._playwright = options.preLaunchedBrowser.attribution.playwright;
this._dontReuse(options.preLaunchedBrowser);
}
if (options.preLaunchedAndroidDevice)
this._playwright = options.preLaunchedAndroidDevice._android.attribution.playwright;
this._playwright ??= (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
const browserSemaphore = new import_semaphore.Semaphore(this._options.maxConnections);
const controllerSemaphore = new import_semaphore.Semaphore(1);
const reuseBrowserSemaphore = new import_semaphore.Semaphore(1);
this._wsServer = new import_wsServer.WSServer({
onRequest: (request, response) => {
if (request.method === "GET" && request.url === "/json") {
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify({
wsEndpointPath: this._options.path
}));
return;
}
response.end("Running");
},
onUpgrade: (request, socket) => {
const uaError = userAgentVersionMatchesErrorMessage(request.headers["user-agent"] || "");
if (uaError)
return { error: `HTTP/${request.httpVersion} 428 Precondition Required\r
\r
${uaError}` };
},
onHeaders: (headers) => {
if (process.env.PWTEST_SERVER_WS_HEADERS)
headers.push(process.env.PWTEST_SERVER_WS_HEADERS);
},
onConnection: (request, url, ws, id) => {
const browserHeader = request.headers["x-playwright-browser"];
const browserName = url.searchParams.get("browser") || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null;
const proxyHeader = request.headers["x-playwright-proxy"];
const proxyValue = url.searchParams.get("proxy") || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
const launchOptionsHeader = request.headers["x-playwright-launch-options"] || "";
const launchOptionsHeaderValue = Array.isArray(launchOptionsHeader) ? launchOptionsHeader[0] : launchOptionsHeader;
const launchOptionsParam = url.searchParams.get("launch-options");
let launchOptions = { timeout: import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT };
try {
launchOptions = JSON.parse(launchOptionsParam || launchOptionsHeaderValue);
if (!launchOptions.timeout)
launchOptions.timeout = import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT;
} catch (e) {
}
const isExtension = this._options.mode === "extension";
const allowFSPaths = isExtension;
launchOptions = filterLaunchOptions(launchOptions, allowFSPaths);
if (this._options.artifactsDir)
launchOptions.artifactsDir = this._options.artifactsDir;
if (isExtension) {
const connectFilter = url.searchParams.get("connect");
if (connectFilter) {
if (connectFilter !== "first")
throw new Error(`Unknown connect filter: ${connectFilter}`);
return new import_playwrightConnection.PlaywrightConnection(
browserSemaphore,
new import_serverTransport.WebSocketServerTransport(ws),
false,
this._playwright,
() => this._initConnectMode(id, connectFilter, browserName, launchOptions),
id
);
}
if (url.searchParams.has("debug-controller")) {
return new import_playwrightConnection.PlaywrightConnection(
controllerSemaphore,
new import_serverTransport.WebSocketServerTransport(ws),
true,
this._playwright,
async () => {
throw new Error("shouldnt be used");
},
id
);
}
return new import_playwrightConnection.PlaywrightConnection(
reuseBrowserSemaphore,
new import_serverTransport.WebSocketServerTransport(ws),
false,
this._playwright,
() => this._initReuseBrowsersMode(browserName, launchOptions, id),
id
);
}
if (this._options.mode === "launchServer" || this._options.mode === "launchServerShared") {
if (this._options.preLaunchedBrowser) {
return new import_playwrightConnection.PlaywrightConnection(
browserSemaphore,
new import_serverTransport.WebSocketServerTransport(ws),
false,
this._playwright,
() => this._initPreLaunchedBrowserMode(id),
id
);
}
return new import_playwrightConnection.PlaywrightConnection(
browserSemaphore,
new import_serverTransport.WebSocketServerTransport(ws),
false,
this._playwright,
() => this._initPreLaunchedAndroidMode(id),
id
);
}
return new import_playwrightConnection.PlaywrightConnection(
browserSemaphore,
new import_serverTransport.WebSocketServerTransport(ws),
false,
this._playwright,
() => this._initLaunchBrowserMode(browserName, proxyValue, launchOptions, id),
id
);
}
});
}
async _initReuseBrowsersMode(browserName, launchOptions, id) {
import_utils.debugLogger.log("server", `[${id}] engaged reuse browsers mode for ${browserName}`);
const requestedOptions = launchOptionsHash(launchOptions);
let browser = this._playwright.allBrowsers().find((b) => {
if (b.options.name !== browserName)
return false;
if (this._dontReuseBrowsers.has(b))
return false;
const existingOptions = launchOptionsHash({ ...b.options.originalLaunchOptions, timeout: import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT });
return existingOptions === requestedOptions;
});
for (const b of this._playwright.allBrowsers()) {
if (b === browser)
continue;
if (this._dontReuseBrowsers.has(b))
continue;
if (b.options.name === browserName && b.options.channel === launchOptions.channel)
await b.close({ reason: "Connection terminated" });
}
if (!browser) {
const browserType = this._playwright[browserName || "chromium"];
const controller = new import_progress.ProgressController();
browser = await controller.run((progress) => browserType.launch(progress, {
...launchOptions,
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS
}), launchOptions.timeout);
}
return {
preLaunchedBrowser: browser,
denyLaunch: true,
dispose: async () => {
for (const context of browser.contexts()) {
if (!context.pages().length)
await context.close({ reason: "Connection terminated" });
}
}
};
}
async _initConnectMode(id, filter, browserName, launchOptions) {
browserName ??= "chromium";
import_utils.debugLogger.log("server", `[${id}] engaged connect mode`);
let browser = this._playwright.allBrowsers().find((b) => b.options.name === browserName);
if (!browser) {
const browserType = this._playwright[browserName];
const controller = new import_progress.ProgressController();
browser = await controller.run((progress) => browserType.launch(progress, launchOptions), launchOptions.timeout);
this._dontReuse(browser);
}
return {
preLaunchedBrowser: browser,
denyLaunch: true,
sharedBrowser: true
};
}
async _initPreLaunchedBrowserMode(id) {
import_utils.debugLogger.log("server", `[${id}] engaged pre-launched (browser) mode`);
const browser = this._options.preLaunchedBrowser;
for (const b of this._playwright.allBrowsers()) {
if (b !== browser)
await b.close({ reason: "Connection terminated" });
}
return {
preLaunchedBrowser: browser,
socksProxy: this._options.preLaunchedSocksProxy,
sharedBrowser: this._options.mode === "launchServerShared",
denyLaunch: true
};
}
async _initPreLaunchedAndroidMode(id) {
import_utils.debugLogger.log("server", `[${id}] engaged pre-launched (Android) mode`);
const androidDevice = this._options.preLaunchedAndroidDevice;
return {
preLaunchedAndroidDevice: androidDevice,
denyLaunch: true
};
}
async _initLaunchBrowserMode(browserName, proxyValue, launchOptions, id) {
import_utils.debugLogger.log("server", `[${id}] engaged launch mode for "${browserName}"`);
let socksProxy;
if (proxyValue) {
socksProxy = new import_socksProxy.SocksProxy();
socksProxy.setPattern(proxyValue);
launchOptions.socksProxyPort = await socksProxy.listen(0);
import_utils.debugLogger.log("server", `[${id}] started socks proxy on port ${launchOptions.socksProxyPort}`);
} else {
launchOptions.socksProxyPort = void 0;
}
const browserType = this._playwright[browserName];
const controller = new import_progress.ProgressController();
const browser = await controller.run((progress) => browserType.launch(progress, launchOptions), launchOptions.timeout);
this._dontReuseBrowsers.add(browser);
return {
preLaunchedBrowser: browser,
socksProxy,
denyLaunch: true,
dispose: async () => {
await browser.close({ reason: "Connection terminated" });
socksProxy?.close();
}
};
}
_dontReuse(browser) {
this._dontReuseBrowsers.add(browser);
browser.on(import_browser.Browser.Events.Disconnected, () => {
this._dontReuseBrowsers.delete(browser);
});
}
async listen(port = 0, hostname) {
return this._wsServer.listen(port, hostname, this._options.path);
}
async close() {
await this._wsServer.close();
}
}
function userAgentVersionMatchesErrorMessage(userAgent) {
const match = userAgent.match(/^Playwright\/(\d+\.\d+\.\d+)/);
if (!match) {
return;
}
const received = match[1].split(".").slice(0, 2).join(".");
const expected = (0, import_userAgent.getPlaywrightVersion)(true);
if (received !== expected) {
return (0, import_ascii.wrapInASCIIBox)([
`Playwright version mismatch:`,
` - server version: v${expected}`,
` - client version: v${received}`,
``,
`If you are using VSCode extension, restart VSCode.`,
``,
`If you are connecting to a remote service,`,
`keep your local Playwright version in sync`,
`with the remote service version.`,
``,
`<3 Playwright Team`
].join("\n"), 1);
}
}
function launchOptionsHash(options) {
const copy = { ...options };
for (const k of Object.keys(copy)) {
const key = k;
if (copy[key] === defaultLaunchOptions[key])
delete copy[key];
}
for (const key of optionsThatAllowBrowserReuse)
delete copy[key];
return JSON.stringify(copy);
}
function filterLaunchOptions(options, allowFSPaths) {
return {
channel: options.channel,
args: options.args,
ignoreAllDefaultArgs: options.ignoreAllDefaultArgs,
ignoreDefaultArgs: options.ignoreDefaultArgs,
timeout: options.timeout,
headless: options.headless,
proxy: options.proxy,
chromiumSandbox: options.chromiumSandbox,
firefoxUserPrefs: options.firefoxUserPrefs,
slowMo: options.slowMo,
executablePath: (0, import_utils.isUnderTest)() || allowFSPaths ? options.executablePath : void 0,
downloadsPath: allowFSPaths ? options.downloadsPath : void 0,
artifactsDir: (0, import_utils.isUnderTest)() || allowFSPaths ? options.artifactsDir : void 0
};
}
const defaultLaunchOptions = {
ignoreAllDefaultArgs: false,
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
headless: true
};
const optionsThatAllowBrowserReuse = [
"headless",
"timeout",
"tracesDir",
"artifactsDir"
];
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PlaywrightServer
});
@@ -0,0 +1,73 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var playwrightWebSocketServer_exports = {};
__export(playwrightWebSocketServer_exports, {
PlaywrightWebSocketServer: () => PlaywrightWebSocketServer
});
module.exports = __toCommonJS(playwrightWebSocketServer_exports);
var import_playwrightConnection = require("./playwrightConnection");
var import_serverTransport = require("./serverTransport");
var import_debugLogger = require("../server/utils/debugLogger");
var import_browser = require("../server/browser");
var import_utils = require("../utils");
var import_wsServer = require("../server/utils/wsServer");
class PlaywrightWebSocketServer {
constructor(browser, path) {
this._browser = browser;
browser.on(import_browser.Browser.Events.Disconnected, () => this.close());
const semaphore = new import_utils.Semaphore(Infinity);
this._wsServer = new import_wsServer.WSServer({
onRequest: (request, response) => {
response.end("Running");
},
onUpgrade: () => void 0,
onHeaders: () => {
},
onConnection: (request, url, ws, id) => {
import_debugLogger.debugLogger.log("server", `[${id}] ws client connected`);
return new import_playwrightConnection.PlaywrightConnection(
semaphore,
new import_serverTransport.WebSocketServerTransport(ws),
false,
this._browser.attribution.playwright,
() => this._initPreLaunchedBrowserMode(id),
id
);
}
});
}
async _initPreLaunchedBrowserMode(id) {
import_debugLogger.debugLogger.log("server", `[${id}] engaged pre-launched (browser) ws mode`);
return {
preLaunchedBrowser: this._browser,
sharedBrowser: true,
denyLaunch: true
};
}
async listen(port = 0, hostname, path) {
return await this._wsServer.listen(port, hostname, path || "/");
}
async close() {
await this._wsServer.close();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PlaywrightWebSocketServer
});
@@ -0,0 +1,96 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var serverTransport_exports = {};
__export(serverTransport_exports, {
SocketServerTransport: () => SocketServerTransport,
WebSocketServerTransport: () => WebSocketServerTransport
});
module.exports = __toCommonJS(serverTransport_exports);
var import_events = require("events");
class WebSocketServerTransport {
constructor(ws) {
this._ws = ws;
}
send(message) {
this._ws.send(message);
}
close(reason) {
this._ws.close(reason?.code, reason?.reason);
}
on(event, handler) {
this._ws.on(event, handler);
}
isClosed() {
return this._ws.readyState === this._ws.CLOSING || this._ws.readyState === this._ws.CLOSED;
}
}
class SocketServerTransport extends import_events.EventEmitter {
constructor(socket) {
super();
this._closed = false;
this._pendingBuffers = [];
this._socket = socket;
socket.on("data", (buffer) => this._dispatch(buffer));
socket.on("close", () => {
this._closed = true;
super.emit("close");
});
socket.on("error", (error) => {
super.emit("error", error);
});
}
send(message) {
if (this._closed)
return;
this._socket.write(message);
this._socket.write("\0");
}
close(reason) {
if (this._closed)
return;
this._closed = true;
this._socket.end();
}
isClosed() {
return this._closed;
}
_dispatch(buffer) {
let end = buffer.indexOf("\0");
if (end === -1) {
this._pendingBuffers.push(buffer);
return;
}
this._pendingBuffers.push(buffer.slice(0, end));
const message = Buffer.concat(this._pendingBuffers).toString();
super.emit("message", message);
let start = end + 1;
end = buffer.indexOf("\0", start);
while (end !== -1) {
super.emit("message", buffer.toString(void 0, start, end));
start = end + 1;
end = buffer.indexOf("\0", start);
}
this._pendingBuffers = [buffer.slice(start)];
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SocketServerTransport,
WebSocketServerTransport
});

Some files were not shown because too many files have changed in this diff Show More