使用 Playwright 模拟 API
简介
Web API 通常以 HTTP 端点实现。Playwright 提供 API,用于模拟和修改 HTTP、HTTPS 网络流量。页面发出的任何请求,包括 XHR 与 fetch,都可以跟踪、修改和模拟。还可以使用 HAR 文件模拟请求;HAR 中可以包含页面发出的多个网络请求。 ## 模拟 API 请求
下列代码拦截所有发往 */**/api/v1/fruits 的请求,并返回自定义响应,不向真实 API 发出请求。测试访问使用这条模拟路由的 URL,并断言页面出现模拟数据。 #### JavaScript
test("mocks a fruit and doesn't call api", async ({ page }) => {
// Mock the api call before navigating
await page.route('*/**/api/v1/fruits', async route => {
const json = [{ name: 'Strawberry', id: 21 }];
await route.fulfill({ json });
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the Strawberry fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});
Python(异步)
“`python async async def test_mock_the_fruit_api(page: Page): async def handle(route: Route): json = [{“name”: “Strawberry”, “id”: 21}] # fulfill the route with the mock data await route.fulfill(json=json)
# Intercept the route to the fruit API
await page.route("*/**/api/v1/fruits", handle)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Strawberry fruit is visible
await expect(page.get_by_text("Strawberry")).to_be_visible()
#### Python(同步)
```python sync
def test_mock_the_fruit_api(page: Page):
def handle(route: Route):
json = [{"name": "Strawberry", "id": 21}]
# fulfill the route with the mock data
route.fulfill(json=json)
# Intercept the route to the fruit API
page.route("*/**/api/v1/fruits", handle)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Strawberry fruit is visible
expect(page.get_by_text("Strawberry")).to_be_visible()
C
// Intercept the route to the fruit API
await page.RouteAsync("*/**/api/v1/fruits", async route => {
var json = new[] { new { name = "Strawberry", id = 21 } };
// fulfill the route with the mock data
await route.FulfillAsync(new()
{
Json = json
});
});
// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");
// Assert that the Strawberry fruit is visible
await Expect(page.GetByTextAsync("Strawberry")).ToBeVisibleAsync();
Java
// Intercept the route to the fruit API
page.route("https://fruit.ceo/api/breeds/image/random", route -> {
List<Dictionary<String, Object>> data = new ArrayList<Dictionary<String, Object>>();
Hashtable<String, Object> dict = new Hashtable<String, Object>();
dict.put("name", "Strawberry");
dict.put("id", 21);
data.add(dict);
// fulfill the route with the mock data
route.fulfill(RequestOptions.create().setData(data));
});
// Go to the page
page.navigate("https://demo.playwright.dev/api-mocking");
// Assert that the Strawberry fruit is visible
assertThat(page.getByText("Strawberry")).isVisible();
从示例测试的跟踪记录可以看到,API 从未被调用,请求由模拟数据满足。 更多内容见高级网络操作。 ## 修改 API 响应
有时必须发出真实 API 请求,但需要修补响应以获得可重复的测试。 此时不必完全模拟请求,而是先执行请求,再使用修改后的响应满足它。
下面的示例拦截水果 API 调用,并向数据中添加名为“Loquat”的新水果,然后访问 URL 并断言该数据存在:
JavaScript
test('gets the json from api and adds a new fruit', async ({ page }) => {
// Get the response and add to it
await page.route('*/**/api/v1/fruits', async route => {
const response = await route.fetch();
const json = await response.json();
json.push({ name: 'Loquat', id: 100 });
// Fulfill using the original response, while patching the response body
// with the given JSON object.
await route.fulfill({ response, json });
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the new fruit is visible
await expect(page.getByText('Loquat', { exact: true })).toBeVisible();
});
Python(异步)
“`python async async def test_gets_the_json_from_api_and_adds_a_new_fruit(page: Page): async def handle(route: Route): response = await route.fetch() json = await response.json() json.append({ “name”: “Loquat”, “id”: 100}) # Fulfill using the original response, while patching the response body # with the given JSON object. await route.fulfill(response=response, json=json) await page.route(“https://demo.playwright.dev/api-mocking/api/v1/fruits”, handle)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the new fruit is visible
await expect(page.get_by_text("Loquat", exact=True)).to_be_visible()
#### Python(同步)
```python sync
def test_gets_the_json_from_api_and_adds_a_new_fruit(page: Page):
def handle(route: Route):
response = route.fetch()
json = response.json()
json.append({ "name": "Loquat", "id": 100})
# Fulfill using the original response, while patching the response body
# with the given JSON object.
route.fulfill(response=response, json=json)
page.route("https://demo.playwright.dev/api-mocking/api/v1/fruits", handle)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the new fruit is visible
expect(page.get_by_text("Loquat", exact=True)).to_be_visible()
C
await page.RouteAsync("*/**/api/v1/fruits", async (route) => {
var response = await route.FetchAsync();
var fruits = await response.JsonAsync<Fruit[]>();
fruits.Add(new Fruit() { Name = "Loquat", Id = 100 });
// Fulfill using the original response, while patching the response body
// with the given JSON object.
await route.FulfillAsync(new ()
{
Response = response,
Json = fruits
});
}
);
// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");
// Assert that the Loquat fruit is visible
await Expect(page.GetByTextAsync("Loquat", new () { Exact = true })).ToBeVisibleAsync();
Java
page.route("*/**/api/v1/fruits", route -> {
Response response = route.fetch();
byte[] json = response.body();
JsonObject parsed = new Gson().fromJson(new String(json), JsonObject.class);
parsed.add(new JsonObject().add("name", "Loquat").add("id", 100));
// Fulfill using the original response, while patching the response body
// with the given JSON object.
route.fulfill(new Route.FulfillOptions().setResponse(response).setBody(parsed.toString()));
});
// Go to the page
page.navigate("https://demo.playwright.dev/api-mocking");
// Assert that the Loquat fruit is visible
assertThat(page.getByText("Loquat", new Page.GetByTextOptions().setExact(true))).isVisible();
在测试跟踪中可以看到,API 确实被调用,而响应被修改了。 检查响应可以看到,新水果已经加入列表。
更多内容见高级网络操作。 ## 使用 HAR 文件模拟
HAR 是 HTTP Archive 文件,记录页面加载时发出的所有网络请求,包括请求头、响应头、cookie、内容、计时等。可用它在测试中模拟网络请求。需要:
- 录制 HAR 文件。
- 将 HAR 文件与测试一起提交。
- 在测试中使用保存的 HAR 文件路由请求。 ### 录制 HAR 文件
使用 Page.routeFromHAR 或 BrowserContext.routeFromHAR 录制 HAR。该方法接收 HAR 文件路径及可选的选项对象。 选项对象可以包含 URL,使 HAR 文件只服务于 URL 匹配指定 glob 模式的请求。若不指定,则所有请求都由 HAR 文件提供。 将 update 设为 true,会用真实网络信息创建或更新 HAR,而不是从 HAR 提供响应。创建测试时,可用它把真实数据填入 HAR。 #### JavaScript
另一种方式是在创建浏览器上下文时,使用 Browser.newContext 的 recordHar 选项。这样可以捕获整个上下文中的全部网络流量,直到上下文关闭。 #### C#、Java 与 Python
另一种方式是在创建浏览器上下文时,使用 Browser.newContext 的 recordHarPath 选项。这样可以捕获整个上下文中的全部网络流量,直到上下文关闭。 #### JavaScript
test('records or updates the HAR file', async ({ page }) => {
// Get the response from the HAR file
await page.routeFromHAR('./hars/fruit.har', {
url: '*/**/api/v1/fruits',
update: true,
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});
Python(异步)
“`python async async def test_records_or_updates_the_har_file(page: Page): # Get the response from the HAR file await page.route_from_har(“./hars/fruit.har”, url=“*/**/api/v1/fruits”, update=True)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the fruit is visible
await expect(page.get_by_text("Strawberry")).to_be_visible()
#### Python(同步)
```python sync
def test_records_or_updates_the_har_file(page: Page):
# Get the response from the HAR file
page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=True)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the fruit is visible
expect(page.get_by_text("Strawberry")).to_be_visible()
C
// Get the response from the HAR file
await page.RouteFromHARAsync("./hars/fruit.har", new () {
Url = "*/**/api/v1/fruits",
Update = true,
});
// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");
// Assert that the fruit is visible
await Expect(page.GetByText("Strawberry")).ToBeVisibleAsync();
Java
// Get the response from the HAR file
page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions()
.setUrl("*/**/api/v1/fruits")
.setUpdate(true)
);
// Go to the page
page.navigate("https://demo.playwright.dev/api-mocking");
// Assert that the fruit is visible
assertThat(page.getByText("Strawberry")).isVisible();
修改 HAR 文件
录制后,可以打开 hars 文件夹中以哈希命名的 .txt 文件,编辑其中的 JSON 来修改 HAR。应将这个文件提交到版本控制。每次以 update: true 运行测试,都会使用 API 请求更新 HAR。
JSON
[
{
"name": "Playwright",
"id": 100
},
// ... other fruits
]
从 HAR 回放
录制 HAR 并修改模拟数据后,即可在测试中用它提供匹配的响应。只需关闭或移除 update 选项。测试将使用 HAR,而不再访问真实 API。 #### JavaScript
test('gets the json from HAR and checks the new fruit has been added', async ({ page }) => {
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.routeFromHAR('./hars/fruit.har', {
url: '*/**/api/v1/fruits',
update: false,
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the Playwright fruit is visible
await expect(page.getByText('Playwright', { exact: true })).toBeVisible();
});
Python(异步)
“`python async async def test_gets_the_json_from_har_and_checks_the_new_fruit_has_been_added(page: Page): # Replay API requests from HAR. # Either use a matching response from the HAR, # or abort the request if nothing matches. await page.route_from_har(“./hars/fruit.har”, url=“*/**/api/v1/fruits”, update=False) # Go to the page await page.goto(“https://demo.playwright.dev/api-mocking”)
# Assert that the Playwright fruit is visible
await expect(page.get_by_text("Playwright", exact=True)).to_be_visible()
#### Python(同步)
```python sync
def test_gets_the_json_from_har_and_checks_the_new_fruit_has_been_added(page: Page):
# Replay API requests from HAR.
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=False)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Playwright fruit is visible
expect(page.get_by_text("Playwright", exact=True)).to_be_visible()
C
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.RouteFromHARAsync("./hars/fruit.har", new ()
{
Url = "*/**/api/v1/fruits",
Update = false,
}
);
// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");
// Assert that the Playwright fruit is visible
await page.ExpectByTextAsync("Playwright", new() { Exact = true }).ToBeVisibleAsync();
Java
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions()
.setUrl("*/**/api/v1/fruits")
.setUpdate(false)
);
// Go to the page
page.navigate("https://demo.playwright.dev/api-mocking");
// Assert that the Playwright fruit is visible
assertThat(page.getByText("Playwright", new Page.GetByTextOptions()
.setExact(true))).isVisible();
测试跟踪显示,路由由 HAR 文件满足,没有调用 API。 检查响应可以看到,新水果已添加到 JSON 中;这是手动修改
hars 文件夹中以哈希命名的 .txt 文件的结果。 HAR 回放严格匹配 URL 与 HTTP 方法。对于 POST 请求,还会严格匹配 POST 载荷。如果多条记录匹配同一个请求,则选择请求头匹配最多的记录。产生重定向的记录会被自动跟随。 与录制时一样,若 HAR 文件名以
.zip 结尾,便被视为归档文件,其中包括 HAR 以及分别存储的网络载荷。也可以解压归档,手动编辑载荷或 HAR 日志,再指向解压后的 HAR 文件。所有载荷路径均相对于文件系统中解压后的 HAR 文件解析。 #### 使用命令行录制 HAR
推荐使用 update 选项为测试录制 HAR,也可以通过 Playwright CLI 录制。
使用 Playwright CLI 打开浏览器并传入 --save-har 以生成 HAR 文件。可选用 --save-har-glob,仅保存关心的请求,例如 API 端点。若 HAR 文件名以 .zip 结尾,产物会作为独立文件写入,并压缩到一个 ZIP 中。 #### bash js
bash js # Save API requests from example.com as "example.har" archive. npx playwright open --save-har=example.har --save-har-glob="**/api/**" https://example.com
bash java
bash java # Save API requests from example.com as "example.har" archive. mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="open --save-har=example.har --save-har-glob='**/api/**' https://example.com" #### bash python
bash python # Save API requests from example.com as "example.har" archive. playwright open --save-har=example.har --save-har-glob="**/api/**" https://example.com
bash csharp
bash csharp # Save API requests from example.com as "example.har" archive. pwsh bin/Debug/netX/playwright.ps1 open --save-har=example.har --save-har-glob="**/api/**" https://example.com
更多内容见高级网络操作。 ## 模拟 WebSocket
下列代码拦截 WebSocket 连接,模拟整个通信过程,而不连接真实服务器。本例以 "response" 响应 "request"。
JavaScript
await page.routeWebSocket('wss://example.com/ws', ws => {
ws.onMessage(message => {
if (message === 'request')
ws.send('response');
});
});
Java
page.routeWebSocket("wss://example.com/ws", ws -> {
ws.onMessage(frame -> {
if ("request".equals(frame.text()))
ws.send("response");
});
});
Python(异步)
“`python async def message_handler(ws: WebSocketRoute, message: Union[str, bytes]): if message == “request”: ws.send(“response”)
await page.route_web_socket(“wss://example.com/ws”, lambda ws: ws.on_message( lambda message: message_handler(ws, message) ))
#### Python(同步)
```python sync
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
ws.send("response")
page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
lambda message: message_handler(ws, message)
))
C
await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
ws.OnMessage(frame => {
if (frame.Text == "request")
ws.Send("response");
});
});
也可以连接真实服务器,同时拦截中间消息,对消息进行修改或阻止。下面的例子修改页面发给服务器的部分消息,其余消息保持不变。
JavaScript
await page.routeWebSocket('wss://example.com/ws', ws => {
const server = ws.connectToServer();
ws.onMessage(message => {
if (message === 'request')
server.send('request2');
else
server.send(message);
});
});
Java
page.routeWebSocket("wss://example.com/ws", ws -> {
WebSocketRoute server = ws.connectToServer();
ws.onMessage(frame -> {
if ("request".equals(frame.text()))
server.send("request2");
else
server.send(frame.text());
});
});
Python(异步)
“`python async def message_handler(server: WebSocketRoute, message: Union[str, bytes]): if message == “request”: server.send(“request2”) else: server.send(message) def handler(ws: WebSocketRoute): server = ws.connect_to_server() ws.on_message(lambda message: message_handler(server, message))
await page.route_web_socket(“wss://example.com/ws”, handler)
#### Python(同步)
```python sync
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
server.send("request2")
else:
server.send(message)
def handler(ws: WebSocketRoute):
server = ws.connect_to_server()
ws.on_message(lambda message: message_handler(server, message))
page.route_web_socket("wss://example.com/ws", handler)
C
await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
var server = ws.ConnectToServer();
ws.OnMessage(frame => {
if (frame.Text == "request")
server.Send("request2");
else
server.Send(frame.Text);
});
});
详情参阅 WebSocketRoute。
来源与许可
Playwright 官方文档源码:Mock APIs,Microsoft 与 Playwright 贡献者,Apache License 2.0。2026-10-03 中文翻译及静态排版转换;保留各语言全部代码与原始截图链接,示例未执行。原文 JSON 片段包含注释,为讲解用片段,不应作为严格 JSON 直接解析。
完整许可证原文
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.











暂无评论内容