diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json
index b5340a3c..38dd4bc4 100644
--- a/assets/javascripts/news.json
+++ b/assets/javascripts/news.json
@@ -1,4 +1,8 @@
[
+ [
+ "2024-01-24",
+ "New documentation: Playwright"
+ ],
[
"2024-01-20",
"New documentation: htmx"
diff --git a/lib/docs/filters/playwright/clean_html.rb b/lib/docs/filters/playwright/clean_html.rb
new file mode 100644
index 00000000..cc97294d
--- /dev/null
+++ b/lib/docs/filters/playwright/clean_html.rb
@@ -0,0 +1,29 @@
+module Docs
+ class Playwright
+ class CleanHtmlFilter < Filter
+ def call
+ @doc = at_css('.markdown')
+
+ css('x-search').remove
+ css('hr').remove
+ css('font:contains("Added in")').remove
+ css('.list-anchor').remove
+
+ css('.alert').each do |node|
+ node.name = 'blockquote'
+ end
+
+ css('pre').each do |node|
+ node.content = node.css('.token-line').map(&:content).join("\n")
+ node.remove_attribute('style')
+ node['data-language'] = node.content =~ /\A\s* ? 'html' : 'javascript'
+ node.ancestors('.theme-code-block').first.replace(node)
+ end
+
+ css('*[class]').remove_attribute('class')
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/playwright/entries.rb b/lib/docs/filters/playwright/entries.rb
new file mode 100644
index 00000000..db186500
--- /dev/null
+++ b/lib/docs/filters/playwright/entries.rb
@@ -0,0 +1,23 @@
+module Docs
+ class Playwright
+ class EntriesFilter < Docs::EntriesFilter
+ def get_name
+ at_css('h1').children.select(&:text?).map(&:content).join.strip
+ end
+
+ def type
+ type = at_css('.menu__link--active').content
+ return "#{type}: #{name}" if slug.starts_with?('api/')
+ type
+ end
+
+ def additional_entries
+ css('x-search').each_with_object [] do |node, entries|
+ prev = node.previous_element
+ prev = prev.previous_element until prev['id']
+ entries << [node.text, prev['id']]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/playwright.rb b/lib/docs/scrapers/playwright.rb
new file mode 100644
index 00000000..8187f9c1
--- /dev/null
+++ b/lib/docs/scrapers/playwright.rb
@@ -0,0 +1,27 @@
+module Docs
+ class Playwright < UrlScraper
+ self.name = 'Playwright'
+ self.type = 'simple'
+ self.release = '1.41.1'
+ self.base_url = 'https://playwright.dev/docs/'
+ self.root_path = 'intro'
+ self.links = {
+ home: 'https://playwright.dev/',
+ code: 'https://github.com/microsoft/playwright'
+ }
+
+ # Docusaurus like react_native
+ html_filters.push 'playwright/entries', 'playwright/clean_html'
+ options[:download_images] = false
+
+ # https://github.com/microsoft/playwright/blob/main/LICENSE
+ options[:attribution] = <<-HTML
+ © 2024 Microsoft
+ Licensed under the Apache License, Version 2.0.
+ HTML
+
+ def get_latest_version(opts)
+ get_npm_version('@playwright/test', opts)
+ end
+ end
+end
diff --git a/public/icons/docs/playwright/16.png b/public/icons/docs/playwright/16.png
new file mode 100644
index 00000000..a209989d
Binary files /dev/null and b/public/icons/docs/playwright/16.png differ
diff --git a/public/icons/docs/playwright/16@2x.png b/public/icons/docs/playwright/16@2x.png
new file mode 100644
index 00000000..e621e1f0
Binary files /dev/null and b/public/icons/docs/playwright/16@2x.png differ
diff --git a/public/icons/docs/playwright/SOURCE b/public/icons/docs/playwright/SOURCE
new file mode 100644
index 00000000..e60a4d3f
--- /dev/null
+++ b/public/icons/docs/playwright/SOURCE
@@ -0,0 +1 @@
+https://playwright.dev/img/playwright-logo.svg