HTMLProofer
如果你生成 HTML 文件,那么这个工具可能适合你!
项目范围
HTMLProofer 是一组用于验证 HTML 输出的测试。这些测试检查你的图像引用是否合法、是否包含 alt 标签、内部链接是否正常工作,等等。它旨在成为针对你输出的一体化检查器。
本项目范围内包括任何用于 HTML 文档质量的知名且广泛使用的测试。本项目的一个主要用途是持续集成——因此我们必须拥有可靠的结果。我们通常将正确性置于性能之上。并且,如有必要,我们应该能够将此程序对 HTML 错误的检测追溯到已记录的最佳实践或标准,例如 W3 规范。
第三方模块。 我们希望此产品对持续集成有用,因此我们倾向于避免容易产生误报结果的主观测试,例如拼写检查器、缩进检查器等。如果你想处理这些项目,请参阅自定义测试部分],并考虑以第三方模块的形式添加一个实现。
高级配置。 大多数前端开发人员可以使用我们的命令行程序]来测试他们的 HTML。高级配置将需要使用 Ruby。
安装
将此行添加到你的应用程序的 Gemfile 中:
gem 'html-proofer'
然后执行:
$ bundle install
或者自行安装:
$ gem install html-proofer
注意: 当安装速度至关重要时,请在您的环境中将 NOKOGIRI_USE_SYSTEM_LIBRARIES 设置为 true。这对于提高持续集成构建的速度非常有用。
测试内容?
以下是 HTMLProofer 可以执行的大致全面的检查列表。
图像
img 元素:
- 您的所有图像是否都有 alt 标签
- 您的内部图像引用是否未损坏
- 外部图像是否正在显示
- 您的图像是否为 HTTP
链接
a、link 元素:
- 您的内部链接是否正常工作
- 您的内部哈希引用(
#linkToMe)是否正常工作 - 外部链接是否正常工作
- 您的链接是否为 HTTPS
- 是否启用了 CORS/SRI
脚本
script 元素:
- 您的内部脚本引用是否正常工作
- 外部脚本是否正在加载
- 是否启用了 CORS/SRI
图标
- 您的 favicon 是否有效。
OpenGraph
- OpenGraph 元数据中的图像和 URL 是否有效。
用法
您可以配置 HTMLProofer 在以下位置运行:
- 一个文件
- 一个目录
- 一个目录数组
- 一个链接数组
它也可以通过命令行运行。
检查单个文件
如果您只想检查单个文件,请使用 check_file 方法:
HTMLProofer.check_file("/path/to/a/file.html").run
检查目录
如果您想检查一个目录,请使用 check_directory:
HTMLProofer.check_directory("./out").run
如果要检查多个目录,请使用 check_directories:
HTMLProofer.check_directories(["./one", "./two"]).run
检查链接数组
使用 check_links,你还可以传入一个链接数组:
HTMLProofer.check_links(["https://github.com", "https://jekyllrb.com"]).run
交换信息
有时,您 HTML 中的信息与服务器提供的内容并不一致。在这种情况下,您可以使用 swap_urls 将文件中的 URL 映射为您希望其变成的 URL。例如:
run_proofer(file, :file, swap_urls: { %r{^https//placeholder.com} => "https://website.com" })
在这种情况下,任何匹配 ^https://placeholder.com 的链接都会被转换为 https://website.com。
类似的交换过程也可以用于属性:
run_proofer(file, :file, swap_attributes: { "img" => [["data-src", "src"]] })
在这种情况下,我们是在告诉 HTMLProofer,对于检测到的任何 img 标签,对于任何 src 属性,都将其视为实际上是 src 属性。由于值是一个数组的数组,你可以为每个元素传入任意数量的属性替换。
在命令行中使用
使用此 gem 时,你还会获得一个名为 htmlproofer 的新程序。太棒了!
通过命令行以标志的形式传入选项,如下所示:
htmlproofer --extensions .html.erb ./out
使用 htmlproofer --help 查看所有命令行选项。
命令行的特殊情况
对于需要输入数组的选项,请用引号将值括起来,并且不要使用 任何空格。例如,要排除一个 HTTP 状态码数组,你可以这样做:
htmlproofer --ignore-status-codes "999,401,404" ./out
对于类似 url-ignore 以及其他需要正则表达式数组的选项,
你可以传入如下语法:
htmlproofer --ignore-urls "/www.github.com/,/foo.com/" ./out
由于 swap_urls 有点特殊,你将传入一对 RegEx:String
值。转义序列 \: 应用于生成字面量
:s htmlproofer 会弄清楚你的意思。
htmlproofer --swap-urls "wow:cow,mow:doh" --extensions .html.erb --ignore-urls www.github.com ./out
某些配置选项,例如 --typheous、--cache 或 --swap-attributes,需要格式正确的 JSON。
针对 baseurl 进行调整
如果您的 Jekyll 站点配置了 baseurl,则需要调整生成的 url 验证以适配该配置。最简单的方法是使用
swap_urls 选项。
对于值为 /BASEURL 的 site.baseurl,其在命令行上的形式如下:
htmlproofer --assume-extension ./_site --swap-urls '^/BASEURL/:/'
或在你的 Rakefile
require "html-proofer"
task :test do
sh "bundle exec jekyll build"
options = { swap_urls: "^/BASEURL/:/" }
HTMLProofer.check_directory("./_site", options).run
end
通过 Docker 使用
如果您在安装 Ruby/Nokogumbo 时遇到问题(或不想安装),可以通过 Docker 运行该命令行工具。更多信息请参阅 klakegg/html-proofer。
忽略内容
向任意标签添加 data-proofer-ignore 属性,即可在所有检查中忽略该标签。
<a href="https://notareallink" data-proofer-ignore>Not checked.</a>
这同样适用于父元素,一直向上到 <html> 标签:
<div data-proofer-ignore>
<a href="https://notareallink">Not checked because of parent.</a>
</div>
忽略新文件
假设你的拉取请求中有一些新文件,而你的测试失败是因为指向这些文件的链接尚未生效。你可以做的其中一件事是,针对你的基础分支运行 diff,并显式忽略这些新文件,如下所示:
directories = ['content']
merge_base = %x(git merge-base origin/production HEAD).chomp
diffable_files = %x(git diff -z --name-only --diff-filter=AC #{merge_base}).split("\0")
diffable_files = diffable_files.select do |filename|
next true if directories.include?(File.dirname(filename))
filename.end_with?(".md")
end.map { |f| Regexp.new(File.basename(f, File.extname(f))) }
HTMLProofer.check_directory("./output", { ignore_urls: diffable_files }).run
配置
HTMLProofer 构造函数接受一个可选的附加选项哈希:
| 选项 | 描述 | 默认值 |
|---|---|---|
allow_hash_href | 如果 true,则假设 href="#" 锚点有效 | true |
allow_missing_href | 如果 true,则不标记缺少 href 的 a 标签。在 HTML5 中,这在技术上是允许的,但也可能是人为错误。 | false |
assume_extension | 自动为内部链接的文件添加指定的扩展名,以支持无扩展名的 URL(大多数服务器均支持) | .html |
checks | 一个字符串数组,指示您希望运行哪些检查 | ['Links', 'Images', 'Scripts'] |
check_external_hash | 检查外部哈希是否存在(即使网页存在) | true |
check_internal_hash | 检查内部哈希是否存在(即使网页存在) | true |
check_sri | 检查 <link> 和 <script> 外部资源是否使用 SRI | false |
directory_index_file | 设置当链接指向目录时要查找的文件。(如果存在,则覆盖 directory_index_files。) | index.html |
directory_index_files | 设置当链接指向目录时要查找的文件。 | ['index.html'] |
disable_external | 如果为 true,则不运行外部链接检查器 | false |
enforce_https | 如果链接未标记为 https,则判定该链接失败。 | true |
extensions | 一个字符串数组,指示您希望检查的文件扩展名(包括点号) | ['.html'] |
ignore_empty_alt | 如果为 true,则忽略 alt 标签为空或缺失的图片(换句话说,<img alt> 和 <img alt=""> 是有效的;将此设置为 false 以标记这些情况) | true |
ignore_files | 一个包含可安全忽略的文件路径的字符串或正则表达式数组。 | [] |
ignore_empty_mailto | 如果为 true,则允许不包含电子邮件地址的 mailto: href。 | false |
ignore_missing_alt | 如果为 true,则忽略 alt 标签缺失的图片 | false |
ignore_status_codes | 一个表示要忽略的状态码的数字数组。 | [] |
ignore_urls | 一个包含可安全忽略的 URL 的字符串或正则表达式数组。这会影响所有 HTML 属性,例如图片上的 alt 标签。 | [] |
log_level | 设置日志级别,由 Yell 决定。为 :debug、:info、:warn、:error 或 :fatal 之一。 | :info |
only_4xx | 仅报告状态码在 4xx 范围内的链接错误。 | false |
root_dir | 提供 html 文件的目录的绝对路径。 | "" |
swap_attributes | 将元素名称映射到首选检查属性的 JSON 格式配置 | {} |
swap_urls | 包含 RegExp => String 键值对的哈希。它通过 gsub 将匹配 RegExp 的 URL 转换为 String。 | {} |
此外,还有几个“命名空间”选项。这些是:
:typhoeus/:hydra:cache
配置 Typhoeus 和 Hydra
Typhoeus 用于向外部 URL 发起快速、并行的请求。您可以通过 :typhoeus 的 options 命名空间,传入 Typhoeus 的任意选项以用于外部链接检查。例如:
HTMLProofer.new("out/", { extensions: [".htm"], typhoeus: { verbose: true, ssl_verifyhost: 2 } })
这将 HTMLProofer 的扩展设置为使用 .htm,为 Typhoeus 提供配置以使其输出详细信息,并使用特定的 SSL 设置。有关它可以接收的选项的更多信息,请参阅 Typhoeus 文档。
类似地,你可以传入一个带有 Hydra 哈希配置的 :hydra 选项。
默认值为:
{
typhoeus:
{
followlocation: true,
connecttimeout: 10,
timeout: 30,
},
hydra: { max_concurrency: 50 },
}
在 CLI 中,你可以提供 --typhoeus 或 --hydra 参数来设置配置。这使用 JSON.parse 进行解析,并映射到默认配置值之上,以便可以覆盖它们。要在 CLI 上传入上述示例,你需要执行:
htmlproofer --typhoeus '{ "followlocation": true, "connecttimeout": 10, "timeout": 30 }' --hydra '{ "max_concurrency": 50 }'
设置 before-request 回调
您可以提供一个代码块,以在检查外部链接之前设置一些逻辑。例如,假设您希望在每次检查 GitHub URL 时提供一个身份验证令牌。您可以这样做:
proofer = HTMLProofer.check_directory(item, opts)
proofer.before_request do |request|
request.options[:headers]["Authorization"] = "Bearer <TOKEN>" if request.base_url == "https://github.com"
end
proofer.run
当且仅当 base_url 为 https://github.com 时,才会设置 Authorization 标头,对于所有其他 URL 均予以排除。
配置缓存
检查外部 URL 可能会拖慢测试速度。如果您希望加快这一过程,可以为外部和内部链接启用缓存。缓存的含义是,对于在特定时间段内有效的链接,跳过链接检查。
您可以通过传入配置选项 :cache 来启用此功能,该选项包含一个哈希,其中有一个键 :timeframe。:timeframe 定义了缓存在使用前链接被重新检查的时间长度。:timeframe 的格式是一个包含两个键的哈希,即 external 和 internal。每个键都包含一个数字,后跟一个表示时间长度的字母:
M表示月w表示周d表示天h表示小时
例如,传入以下选项意味着“重新检查超过三十天的外部链接”:
{ cache: { timeframe: { external: "30d" } } }
而以下选项表示“重新检查两周前的内部链接”:
{ cache: { timeframe: { internal: "2w" } } }
当然,为了同时支持内部链接和外部链接的缓存,需要提供这两个键。以下配置每两周检查一次外部链接,但每周仅检查一次内部链接:
{ cache: { timeframe: { external: "2w", internal: "1w" } } }
你还可以通过提供 storage_dir 键来更改缓存文件的文件名或存放目录:
{ cache: { cache_file: "stay_cachey.json", storage_dir: "/tmp/html-proofer-cache-money" } }
失败的链接会保留在缓存中,并_始终_重新检查。如果通过,缓存将更新以记录新的时间戳。
缓存仅针对外部链接运行。
如果启用了缓存,HTMLProofer 会写入一个名为 tmp/.htmlproofer/cache.log 的日志文件。您可能应该在版本控制系统中忽略此文件夹。
在 CLI 上,您可以提供 --cache 参数来设置配置。这使用
JSON.parse 进行解析,并映射到默认配置值之上,以便可以覆盖它们。
要在 CLI 上传入上述示例之一,您需要执行:
htmlproofer --cache '{ "timeframe": { "external": "2w", "internal": "1w" } }'
使用持续集成进行缓存
在配置 HTMLProofer 缓存其结果之后(参见上文),您可以在持续集成流程中启用缓存,以加快构建速度,并避免可能链接到的外部网站的速率限制。
在 GitHub Actions 中:
在运行 HTMLProofer 之前,将此步骤添加到您的构建工作流中:
- name: Cache HTMLProofer
id: cache-htmlproofer
uses: actions/cache@v2
with:
path: tmp/.htmlproofer
key: ${{ runner.os }}-htmlproofer
同时确保后续运行 HTMLProofer 的步骤不会返回失败的 shell 状态。您可以尝试类似 html-proof ... || true 的操作。因为 GitHub Actions 中失败的步骤会跳过所有后续步骤。
在 Travis 中:
如果您想在 Travis CI 中启用缓存,请确保将这些行添加到您的 .travis.yml 文件中:
cache:
directories:
- $TRAVIS_BUILD_DIR/tmp/.htmlproofer
有关使用 HTML-Proofer 配合 Travis CI 的更多信息,请参阅此 wiki 页面。
Logging
HTML-Proofer 可以尽可能吵闹或尽可能安静。如果你设置了 :log_level 选项,你可以更好地定义日志级别。
Custom tests
想编写自己的测试?当然,这是可能的!
只需创建一个继承自 HTMLProofer::Check 的类。该子类必须定义一个名为 run 的方法。它会在你的内容上被调用,并负责对你喜欢的任何元素执行验证。当你捕获到一个损坏的问题时,调用 add_failure(message, line: line, content: content) 来解释错误。line 指的是行号,而 content 是损坏元素的节点内容。
如果你正在处理元素的属性(大多数检查都是这样做的),你还希望将 create_element(node) 作为你的测试套件的一部分进行调用。这会构造一个包含你正在迭代的 HTML 元素所有属性的对象,并且也可以直接用于调用 add_failure(message, element: element)。
下面是一个演示这些概念的自定义测试示例。它报告指向 octocat@github.com 的 mailto 链接:
class MailToOctocat < HTMLProofer::Check
def mailto_octocat?
@link.url.raw_attribute == "mailto:octocat@github.com"
end
def run
@html.css("a").each do |node|
@link = create_element(node)
next if @link.ignore?
return add_failure("Don't email the Octocat directly!", element: @link) if mailto_octocat?
end
end
end
别忘了将此新检查项添加到 HTMLProofer 的选项中,例如:
# removes default checks and just runs this one
HTMLProofer.check_directories(["out/"], { checks: ["MailToOctocat"] })
参见我们的第三方自定义类列表,并将您自己的类添加到该列表中。
报告
默认情况下,HTML-Proofer 拥有自己的报告机制,用于在运行结束时打印错误。您可以通过传入 HTMLProofer::Reporter 的自定义子类来选择使用您自己的报告器:
proofer = HTMLProofer.check_directory(item, opts)
proofer.reporter = MyCustomReporter.new(logger: proofer.logger)
proofer.run
您的自定义报告器必须实现 report 函数,以实现您希望看到的行为。logger 关键字参数是可选的。
以编程方式访问失败
运行 HTMLProofer 后,您可以通过 failed_checks 方法访问失败列表。每个失败都是一个 HTMLProofer::Failure 对象,包含有关错误的详细信息:
proofer = HTMLProofer.check_directory("./out")
proofer.run
proofer.failed_checks.each do |failure|
puts "File: #{failure.path}"
puts "Check: #{failure.check_name}"
puts "Description: #{failure.description}"
puts "Line: #{failure.line}"
puts "Status: #{failure.status}" # HTTP status code for external links
puts "Content: #{failure.content}" # Text content of the element
end
访问元素
每个失败还提供对原始 HTMLProofer::Element 对象的访问,该对象允许你访问底层的 Nokogiri 节点及其所有属性:
proofer.failed_checks.each do |failure|
element = failure.element
next if element.nil?
# Access the Nokogiri node directly
node = element.node
puts "Tag name: #{node.name}"
puts "Href: #{node['href']}"
puts "All attributes: #{node.attributes.keys}"
# Use helper methods
puts "Is anchor tag: #{element.a_tag?}"
puts "Is image tag: #{element.img_tag?}"
puts "Link text: #{element.content}"
puts "Line number: #{element.line}"
end
这对于构建自定义报告器、与其他工具集成或以编程方式处理验证结果非常有用。
故障排除
以下是一些简要的片段,用于识别一些你可以规避的常见问题。如需更多信息,请查阅我们的 wiki。
我们的 wiki 页面关于使用 HTML-Proofer 与 Travis CI 可能也很有用。
忽略 SSL 证书
要忽略 SSL 证书,请关闭 Typhoeus 的 SSL 验证:
HTMLProofer.check_directory("out/", {
typhoeus: {
ssl_verifypeer: false,
ssl_verifyhost: 0,
},
}).run
User-Agent
要更改 Typhoeus 使用的 User-Agent:
HTMLProofer.check_directory("out/", {
typhoeus: {
headers: { "User-Agent" => "Mozilla/5.0 (compatible; My New User-Agent)" },
}
}).run
或者,您可以在命令行中通过以下方式指定这些选项:
htmlproofer --typhoeus='{"headers":{"User-Agent":"Mozilla/5.0 (compatible; My New User-Agent)"}}'
Cookies
有时链接会失效,因为它们无法访问 cookies。要修复此问题,您可以使用以下代码片段创建一个 .cookies 文件:
HTMLProofer.check_directory("out/", {
typhoeus: {
cookiefile: ".cookies",
cookiejar: ".cookies",
}
}).run
htmlproofer --typhoeus='{"cookiefile":".cookies","cookiejar":".cookies"}'
正则表达式
若要使用正则表达式排除 URL,请将其置于正斜杠之间,且不要加引号:
HTMLProofer.check_directories(["out/"], {
ignore_urls: [/example.com/],
}).run