Added SDK
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
WI.AuditTestCase = class AuditTestCase extends WI.AuditTestBase
|
||||
{
|
||||
constructor(name, test, options = {})
|
||||
{
|
||||
console.assert(typeof test === "string", test);
|
||||
|
||||
super(name, options);
|
||||
|
||||
this._test = test;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static async fromPayload(payload)
|
||||
{
|
||||
if (typeof payload !== "object" || payload === null)
|
||||
return null;
|
||||
|
||||
if (payload.type !== WI.AuditTestCase.TypeIdentifier)
|
||||
return null;
|
||||
|
||||
if (typeof payload.name !== "string") {
|
||||
WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("name")));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof payload.test !== "string") {
|
||||
WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("test")));
|
||||
return null;
|
||||
}
|
||||
|
||||
let options = {};
|
||||
|
||||
if (typeof payload.description === "string")
|
||||
options.description = payload.description;
|
||||
else if ("description" in payload)
|
||||
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("description")));
|
||||
|
||||
if (typeof payload.supports === "number")
|
||||
options.supports = payload.supports;
|
||||
else if ("supports" in payload)
|
||||
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-number \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("supports")));
|
||||
|
||||
if (typeof payload.setup === "string")
|
||||
options.setup = payload.setup;
|
||||
else if ("setup" in payload)
|
||||
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("setup")));
|
||||
|
||||
if (typeof payload.disabled === "boolean")
|
||||
options.disabled = payload.disabled;
|
||||
|
||||
return new WI.AuditTestCase(payload.name, payload.test, options);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get test()
|
||||
{
|
||||
return this._test;
|
||||
}
|
||||
|
||||
set test(test)
|
||||
{
|
||||
console.assert(this.editable);
|
||||
console.assert(typeof test === "string", test);
|
||||
|
||||
if (test === this._test)
|
||||
return;
|
||||
|
||||
this._test = test;
|
||||
|
||||
this.clearResult();
|
||||
|
||||
this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged);
|
||||
}
|
||||
|
||||
toJSON(key)
|
||||
{
|
||||
let json = super.toJSON(key);
|
||||
json.test = this._test;
|
||||
return json;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
async run()
|
||||
{
|
||||
const levelStrings = Object.values(WI.AuditTestCaseResult.Level);
|
||||
let level = null;
|
||||
let data = {};
|
||||
let metadata = {
|
||||
url: WI.networkManager.mainFrame.url,
|
||||
startTimestamp: null,
|
||||
endTimestamp: null,
|
||||
};
|
||||
let resolvedDOMNodes = null;
|
||||
|
||||
function setLevel(newLevel) {
|
||||
let newLevelIndex = levelStrings.indexOf(newLevel);
|
||||
if (newLevelIndex < 0) {
|
||||
addError(WI.UIString("Return string must be one of %s").format(JSON.stringify(levelStrings)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newLevelIndex <= levelStrings.indexOf(level))
|
||||
return;
|
||||
|
||||
level = newLevel;
|
||||
}
|
||||
|
||||
function addError(value) {
|
||||
setLevel(WI.AuditTestCaseResult.Level.Error);
|
||||
|
||||
if (!data.errors)
|
||||
data.errors = [];
|
||||
|
||||
data.errors.push(value);
|
||||
}
|
||||
|
||||
async function parseResponse(response) {
|
||||
let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget);
|
||||
if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error")) {
|
||||
addError(remoteObject.description);
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteObject.type === "boolean") {
|
||||
setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail);
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteObject.type === "string") {
|
||||
setLevel(remoteObject.value.trim().toLowerCase());
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteObject.type !== "object" || remoteObject.subtype) {
|
||||
addError(WI.UIString("Return value is not an object, string, or boolean"));
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
ownProperties: true,
|
||||
};
|
||||
|
||||
function checkResultProperty(key, value, type, subtype) {
|
||||
function addErrorForValueType(valueType) {
|
||||
let errorString = null;
|
||||
if (valueType === "object" || valueType === "array")
|
||||
errorString = WI.UIString("\u0022%s\u0022 must be an %s");
|
||||
else
|
||||
errorString = WI.UIString("\u0022%s\u0022 must be a %s");
|
||||
addError(errorString.format(key, valueType));
|
||||
}
|
||||
|
||||
if (value.subtype !== subtype) {
|
||||
addErrorForValueType(subtype);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.type !== type) {
|
||||
addErrorForValueType(type);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === "boolean" || type === "string")
|
||||
return value.value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async function resultArrayForEach(key, value, callback) {
|
||||
let array = checkResultProperty(key, value, "object", "array");
|
||||
if (!array)
|
||||
return;
|
||||
|
||||
let arrayProperties = await new Promise((resolve, reject) => array.getPropertyDescriptors(resolve, options));
|
||||
for (let i = 0; i < array.size; ++i) {
|
||||
let arrayPropertyForIndex = arrayProperties.find((arrayProperty) => arrayProperty.name === String(i));
|
||||
if (arrayPropertyForIndex)
|
||||
await callback(arrayPropertyForIndex);
|
||||
}
|
||||
}
|
||||
|
||||
let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptors(resolve, options));
|
||||
for (let property of properties) {
|
||||
let key = property.name;
|
||||
if (key === "__proto__")
|
||||
continue;
|
||||
|
||||
let value = property.value;
|
||||
|
||||
switch (key) {
|
||||
case "level": {
|
||||
let levelString = checkResultProperty(key, value, "string");
|
||||
if (levelString)
|
||||
setLevel(levelString.trim().toLowerCase());
|
||||
break;
|
||||
}
|
||||
|
||||
case "pass":
|
||||
if (checkResultProperty(key, value, "boolean"))
|
||||
setLevel(WI.AuditTestCaseResult.Level.Pass);
|
||||
break;
|
||||
|
||||
case "warn":
|
||||
if (checkResultProperty(key, value, "boolean"))
|
||||
setLevel(WI.AuditTestCaseResult.Level.Warn);
|
||||
break;
|
||||
|
||||
case "fail":
|
||||
if (checkResultProperty(key, value, "boolean"))
|
||||
setLevel(WI.AuditTestCaseResult.Level.Fail);
|
||||
break;
|
||||
|
||||
case "error":
|
||||
if (checkResultProperty(key, value, "boolean"))
|
||||
setLevel(WI.AuditTestCaseResult.Level.Error);
|
||||
break;
|
||||
|
||||
case "unsupported":
|
||||
if (checkResultProperty(key, value, "boolean"))
|
||||
setLevel(WI.AuditTestCaseResult.Level.Unsupported);
|
||||
break;
|
||||
|
||||
case "domNodes":
|
||||
await resultArrayForEach(key, value, async (item) => {
|
||||
if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") {
|
||||
addError(WI.UIString("All items in \u0022%s\u0022 must be valid DOM nodes").format(WI.unlocalizedString("domNodes")));
|
||||
return;
|
||||
}
|
||||
|
||||
let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
|
||||
let domNode = WI.domManager.nodeForId(domNodeId);
|
||||
if (!domNode)
|
||||
return;
|
||||
|
||||
if (!data.domNodes)
|
||||
data.domNodes = [];
|
||||
data.domNodes.push(WI.cssPath(domNode, {full: true}));
|
||||
|
||||
if (!resolvedDOMNodes)
|
||||
resolvedDOMNodes = [];
|
||||
resolvedDOMNodes.push(domNode);
|
||||
});
|
||||
break;
|
||||
|
||||
case "domAttributes":
|
||||
await resultArrayForEach(key, value, (item) => {
|
||||
if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) {
|
||||
addError(WI.UIString("All items in \u0022%s\u0022 must be non-empty strings").format(WI.unlocalizedString("domAttributes")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.domAttributes)
|
||||
data.domAttributes = [];
|
||||
data.domAttributes.push(item.value.value);
|
||||
});
|
||||
break;
|
||||
|
||||
case "errors":
|
||||
await resultArrayForEach(key, value, (item) => {
|
||||
if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") {
|
||||
addError(WI.UIString("All items in \u0022%s\u0022 must be error objects").format(WI.unlocalizedString("errors")));
|
||||
return;
|
||||
}
|
||||
|
||||
addError(item.value.description);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
if (value.objectId) {
|
||||
try {
|
||||
function inspectedPage_stringify() {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
let stringifiedValue = await value.callFunction(inspectedPage_stringify);
|
||||
data[key] = JSON.parse(stringifiedValue.value);
|
||||
} catch {
|
||||
addError(WI.UIString("\u0022%s\u0022 is not JSON serializable").format(key));
|
||||
}
|
||||
} else
|
||||
data[key] = value.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
|
||||
let agentCommandFunction = null;
|
||||
let agentCommandArguments = {};
|
||||
if (target.hasDomain("Audit")) {
|
||||
agentCommandFunction = target.AuditAgent.run;
|
||||
agentCommandArguments.test = this._test;
|
||||
} else {
|
||||
agentCommandFunction = target.RuntimeAgent.evaluate;
|
||||
agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._test.replace(/`/g, "\\`")})\`)(); })()`;
|
||||
agentCommandArguments.objectGroup = WI.AuditTestCase.ObjectGroup;
|
||||
agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
|
||||
}
|
||||
|
||||
try {
|
||||
metadata.startTimestamp = new Date;
|
||||
let response = await agentCommandFunction.invoke(agentCommandArguments);
|
||||
metadata.endTimestamp = new Date;
|
||||
|
||||
if (response.result.type === "object" && response.result.className === "Promise") {
|
||||
if (WI.RuntimeManager.supportsAwaitPromise()) {
|
||||
metadata.asyncTimestamp = metadata.endTimestamp;
|
||||
response = await target.RuntimeAgent.awaitPromise(response.result.objectId);
|
||||
metadata.endTimestamp = new Date;
|
||||
} else {
|
||||
response = null;
|
||||
addError(WI.UIString("Async audits are not supported."));
|
||||
setLevel(WI.AuditTestCaseResult.Level.Unsupported);
|
||||
}
|
||||
}
|
||||
|
||||
if (response)
|
||||
await parseResponse(response);
|
||||
} catch (error) {
|
||||
metadata.endTimestamp = new Date;
|
||||
addError(error.message);
|
||||
}
|
||||
|
||||
if (!level)
|
||||
addError(WI.UIString("Missing result level"));
|
||||
|
||||
let options = {
|
||||
description: this.description,
|
||||
metadata,
|
||||
};
|
||||
if (!isEmptyObject(data))
|
||||
options.data = data;
|
||||
if (resolvedDOMNodes)
|
||||
options.resolvedDOMNodes = resolvedDOMNodes;
|
||||
this.updateResult(new WI.AuditTestCaseResult(this.name, level, options));
|
||||
}
|
||||
};
|
||||
|
||||
WI.AuditTestCase.TypeIdentifier = "test-case";
|
||||
Reference in New Issue
Block a user