-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add Beacon tests http://www.w3.org/TR/beacon/ #4024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>W3C Beacon Basic Blob Test</title> | ||
| <meta name="timeout" content="long"> | ||
| <script src="/resources/testharness.js"></script> | ||
| <script src="/resources/testharnessreport.js"></script> | ||
| </head> | ||
| <body> | ||
| <script src="/common/utils.js"></script> | ||
| <script src="beacon-common.js?pipe=sub"></script> | ||
| <script> | ||
| "use strict"; | ||
| runTests(blobTests); | ||
| </script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>W3C Beacon Basic BufferSource Test</title> | ||
| <meta name="timeout" content="long"> | ||
| <script src="/resources/testharness.js"></script> | ||
| <script src="/resources/testharnessreport.js"></script> | ||
| </head> | ||
| <body> | ||
| <script src="/common/utils.js"></script> | ||
| <script src="beacon-common.js?pipe=sub"></script> | ||
| <script> | ||
| "use strict"; | ||
| runTests(bufferSourceTests); | ||
| </script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>W3C Beacon Basic FormData Test</title> | ||
| <meta name="timeout" content="long"> | ||
| <script src="/resources/testharness.js"></script> | ||
| <script src="/resources/testharnessreport.js"></script> | ||
| </head> | ||
| <body> | ||
| <script src="/common/utils.js"></script> | ||
| <script src="beacon-common.js?pipe=sub"></script> | ||
| <script> | ||
| "use strict"; | ||
| runTests(formDataTests); | ||
| </script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>W3C Beacon Basic String Test</title> | ||
| <meta name="timeout" content="long"> | ||
| <script src="/resources/testharness.js"></script> | ||
| <script src="/resources/testharnessreport.js"></script> | ||
| </head> | ||
| <body> | ||
| <script src="/common/utils.js"></script> | ||
| <script src="beacon-common.js?pipe=sub"></script> | ||
| <script> | ||
| "use strict"; | ||
| runTests(stringTests); | ||
| </script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| "use strict"; | ||
|
|
||
| // Different sizes of payloads to test. | ||
| var smallPayloadSize = 10; | ||
| var mediumPayloadSize = 10000; | ||
| var largePayloadSize = 50000; | ||
| var maxPayloadSize = 65536; // The maximum payload size allowed for a beacon request. | ||
|
|
||
| // String payloads of various sizes sent by sendbeacon. The format of the payloads is a string: | ||
| // <numberOfCharacters>:<numberOfCharacters *'s> | ||
| // ex. "10:**********" | ||
| var smallPayload = smallPayloadSize + ":" + Array(smallPayloadSize).fill('*').join(""); | ||
| var mediumPayload = mediumPayloadSize + ":" + Array(mediumPayloadSize).fill('*').join(""); | ||
| var largePayload = largePayloadSize + ":" + Array(largePayloadSize).fill('*').join(""); | ||
| // Subtract 6 from maxPayloadSize because 65536 is 5 digits, plus 1 more for the ':' | ||
| var maxPayload = (maxPayloadSize - 6) + ":" + Array(maxPayloadSize - 6).fill('*').join("") | ||
|
|
||
| // Test case definitions. | ||
| // id: String containing the unique name of the test case. | ||
| // data: Payload object to send through sendbeacon. | ||
| var noDataTest = { id: "NoData" }; | ||
| var nullDataTest = { id: "NullData", data: null }; | ||
| var undefinedDataTest = { id: "UndefinedData", data: undefined }; | ||
| var smallStringTest = { id: "SmallString", data: smallPayload }; | ||
| var mediumStringTest = { id: "MediumString", data: mediumPayload }; | ||
| var largeStringTest = { id: "LargeString", data: largePayload }; | ||
| var maxStringTest = { id: "MaxString", data: maxPayload }; | ||
| var emptyBlobTest = { id: "EmptyBlob", data: new Blob() }; | ||
| var smallBlobTest = { id: "SmallBlob", data: new Blob([smallPayload]) }; | ||
| var mediumBlobTest = { id: "MediumBlob", data: new Blob([mediumPayload]) }; | ||
| var largeBlobTest = { id: "LargeBlob", data: new Blob([largePayload]) }; | ||
| var maxBlobTest = { id: "MaxBlob", data: new Blob([maxPayload]) }; | ||
| var emptyBufferSourceTest = { id: "EmptyBufferSource", data: new Uint8Array() }; | ||
| var smallBufferSourceTest = { id: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) }; | ||
| var mediumBufferSourceTest = { id: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) }; | ||
| var largeBufferSourceTest = { id: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) }; | ||
| var maxBufferSourceTest = { id: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) }; | ||
| var emptyFormDataTest = { id: "EmptyFormData", data: CreateEmptyFormDataPayload() }; | ||
| var smallFormDataTest = { id: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) }; | ||
| var mediumFormDataTest = { id: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) }; | ||
| var largeFormDataTest = { id: "LargeFormData", data: CreateFormDataFromPayload(largePayload) }; | ||
| // We don't test maxFormData because the extra multipart separators make it difficult to | ||
| // calculate a maxPayload. | ||
|
|
||
| // Test case suites. | ||
| var stringTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, mediumStringTest, largeStringTest, maxStringTest]; | ||
| var blobTests = [emptyBlobTest, smallBlobTest, mediumBlobTest, largeBlobTest, maxBlobTest]; | ||
| var bufferSourceTests = [emptyBufferSourceTest, smallBufferSourceTest, mediumBufferSourceTest, largeBufferSourceTest, maxBufferSourceTest]; | ||
| var formDataTests = [emptyFormDataTest, smallFormDataTest, mediumFormDataTest, largeFormDataTest]; | ||
| var allTests = [].concat(stringTests, blobTests, bufferSourceTests, formDataTests); | ||
|
|
||
| // This special cross section of test cases is meant to provide a slimmer but reasonably- | ||
| // representative set of tests for parameterization across variables (e.g. redirect codes, | ||
| // cors modes, etc.) | ||
| var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, mediumStringTest, mediumBlobTest, mediumBufferSourceTest, mediumFormDataTest]; | ||
|
|
||
| // Build a test lookup table, which is useful when instructing a web worker or an iframe | ||
| // to run a test, so that we don't have to marshal the entire test case across a process boundary. | ||
| var testLookup = {}; | ||
| allTests.forEach(function(testCase) { | ||
| testLookup[testCase.id] = testCase; | ||
| }); | ||
|
|
||
| // Helper function to create an ArrayBuffer representation of a string. | ||
| function CreateArrayBufferFromPayload(payload) { | ||
| var length = payload.length; | ||
| var buffer = new Uint8Array(length); | ||
|
|
||
| for (var i = 0; i < length; i++) { | ||
| buffer[i] = payload.charCodeAt(i); | ||
| } | ||
|
|
||
| return buffer; | ||
| } | ||
|
|
||
| // Helper function to create an empty FormData object. | ||
| function CreateEmptyFormDataPayload() { | ||
| // http://osgvsowi/8344051 - DOM: Workers: Add FormData support to Web Workers | ||
| if (self.document === undefined) { | ||
| return null; | ||
| } | ||
|
|
||
| return new FormData(); | ||
| } | ||
|
|
||
| // Helper function to create a FormData representation of a string. | ||
| function CreateFormDataFromPayload(payload) { | ||
| // http://osgvsowi/8344051 - DOM: Workers: Add FormData support to Web Workers | ||
| if (self.document === undefined) { | ||
| return null; | ||
| } | ||
|
|
||
| var formData = new FormData(); | ||
| formData.append("payload", payload); | ||
| return formData; | ||
| } | ||
|
|
||
| // Initializes a session with a client-generated SID. | ||
| // A "session" is a run of one or more tests. It is used to batch several beacon | ||
| // tests in a way that isolates the server-side session state and makes it easy | ||
| // to poll the results of the tests in one request. | ||
| // testCases: The array of test cases participating in the session. | ||
| function initSession(testCases) { | ||
| return { | ||
| // Provides a unique session identifier to prevent mixing server-side data | ||
| // with other sessions. | ||
| id: self.token(), | ||
| // Dictionary of test name to live testCase object. | ||
| testCaseLookup: {}, | ||
| // Array of testCase objects for iteration. | ||
| testCases: [], | ||
| // Tracks the total number of tests in the session. | ||
| totalCount: testCases.length, | ||
| // Tracks the number of tests for which we have sent the beacon. | ||
| // When it reaches totalCount, we will start polling for results. | ||
| sentCount: 0, | ||
| // Tracks the number of tests for which we have verified the results. | ||
| // When it reaches sentCount, we will stop polling for results. | ||
| doneCount: 0, | ||
| // Helper to add a testCase to the session. | ||
| add: function add(testCase) { | ||
| this.testCases.push(testCase); | ||
| this.testCaseLookup[testCase.id] = testCase; | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| // Schedules async_test's for each of the test cases, treating them as a single session, | ||
| // and wires up the continueAfterSendingBeacon() and waitForResults() calls. | ||
| // The method looks for several "extension" functions in the global scope: | ||
| // - self.buildId: if present, can change the display name of a test. | ||
| // - self.buildBaseUrl: if present, can change the base URL of a beacon target URL (this | ||
| // is the scheme, hostname, and port). | ||
| // - self.buildTargetUrl: if present, can modify a beacon target URL (for example wrap it). | ||
| // Parameters: | ||
| // testCases: An array of test cases. | ||
| function runTests(testCases) { | ||
| var session = initSession(testCases); | ||
|
|
||
| testCases.forEach(function(testCase, testIndex) { | ||
| // Make a copy of the test case as we'll be storing some metadata on it, | ||
| // such as which session it belongs to. | ||
| var testCaseCopy = Object.assign({ session: session }, testCase); | ||
|
|
||
| // Extension point: generate the test id. | ||
| var testId = testCase.id; | ||
| if (self.buildId) { | ||
| testId = self.buildId(testId); | ||
| } | ||
| testCaseCopy.origId = testCaseCopy.id; | ||
| testCaseCopy.id = testId; | ||
| testCaseCopy.index = testIndex; | ||
|
|
||
| session.add(testCaseCopy); | ||
|
|
||
| // Schedule the sendbeacon in an async test. | ||
| async_test(function(test) { | ||
| // Save the testharness.js 'test' object, so that we only have one object | ||
| // to pass around. | ||
| testCaseCopy.test = test; | ||
|
|
||
| // Extension point: generate the beacon URL. | ||
| var baseUrl = "http://{{host}}:{{ports[http][0]}}"; | ||
| if (self.buildBaseUrl) { | ||
| baseUrl = self.buildBaseUrl(baseUrl); | ||
| } | ||
| var targetUrl = `${baseUrl}/beacon/resources/beacon.py?cmd=store&sid=${session.id}&tid=${testId}&tidx=${testIndex}`; | ||
| if (self.buildTargetUrl) { | ||
| targetUrl = self.buildTargetUrl(targetUrl); | ||
| } | ||
| // Attach the URL to the test object for debugging purposes. | ||
| testCaseCopy.url = targetUrl; | ||
|
|
||
| // Extension point: send the beacon immediately, or defer. | ||
| var sendFunc = test.step_func(function sendImmediately(testCase) { | ||
| var sendResult = sendData(testCase); | ||
| continueAfterSendingBeacon(sendResult, testCase); | ||
| }); | ||
| if (self.sendFunc) { | ||
| sendFunc = test.step_func(self.sendFunc); | ||
| } | ||
| sendFunc(testCaseCopy); | ||
| }, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCaseCopy.id}`); | ||
| }); | ||
| } | ||
|
|
||
| // Sends the beacon for a single test. This step is factored into its own function so that | ||
| // it can be called from a web worker. It does not check for results. | ||
| // Note: do not assert from this method, as when called from a worker, we won't have the | ||
| // full testharness.js test context. Instead return 'false', and the main scope will fail | ||
| // the test. | ||
| // Returns the result of the 'sendbeacon()' function call, true or false. | ||
| function sendData(testCase) { | ||
| var sent = false; | ||
| if (testCase.data) { | ||
| sent = self.navigator.sendBeacon(testCase.url, testCase.data); | ||
| } else { | ||
| sent = self.navigator.sendBeacon(testCase.url) | ||
| } | ||
| return sent; | ||
| } | ||
|
|
||
| // Continues a single test after the beacon has been sent for that test. | ||
| // Will trigger waitForResults() for the session if this is the last test | ||
| // in the session to send its beacon. | ||
| // Assumption: will be called on the test's step_func so that assert's do | ||
| // not have to be wrapped. | ||
| function continueAfterSendingBeacon(sendResult, testCase) { | ||
| var session = testCase.session; | ||
|
|
||
| // Recaclulate the sent vs. total counts. | ||
| if (sendResult) { | ||
| session.sentCount++; | ||
| } else { | ||
| session.totalCount--; | ||
| } | ||
|
|
||
| // If this was the last test in the session to send its beacon, start polling for results. | ||
| // Note that we start polling even if just one test in the session sends successfully, | ||
| // so that if any of the others fail, we still get results from the tests that did send. | ||
| if (session.sentCount == session.totalCount) { | ||
| // Exit the current test's execution context in order to run the poll | ||
| // loop from the harness context. | ||
| step_timeout(waitForResults.bind(this, session), 0); | ||
| } | ||
|
|
||
| // Now fail this test if the beacon did not send. It will be excluded from the poll | ||
| // loop because of the calculation adjustment above. | ||
| assert_true(sendResult, "'sendbeacon' function call must succeed"); | ||
| } | ||
|
|
||
| // Kicks off an asynchronous monitor to poll the server for test results. As we | ||
| // verify that the server has received and validated a beacon, we will complete | ||
| // its testharness test. | ||
| function waitForResults(session) { | ||
| // Poll for status until all of the results come in. | ||
| fetch(`resources/beacon.py?cmd=stat&sid=${session.id}&tidx_min=0&tidx_max=${session.totalCount-1}`).then( | ||
| function(response) { | ||
| // Parse as text(), not json(), so that we can log the raw response if | ||
| // it's invalid. | ||
| response.text().then(function(rawResponse) { | ||
| // Check that we got a response we expect and know how to handle. | ||
| var results; | ||
| var failure; | ||
| try { | ||
| results = JSON.parse(rawResponse); | ||
|
|
||
| if (results.length === undefined) { | ||
| failure = `bad validation response schema: rawResponse='${rawResponse}'`; | ||
| } | ||
| } catch (e) { | ||
| failure = `bad validation response: rawResponse='${rawResponse}', got parse error '${e}'`; | ||
| } | ||
|
|
||
| if (failure) { | ||
| // At this point we can't deterministically get results for all of the | ||
| // tests in the session, so fail the entire session. | ||
| failSession(session, failure); | ||
| return; | ||
| } | ||
|
|
||
| // The 'stat' call will return an array of zero or more results | ||
| // of sendbeacon() calls that the server has received and validated. | ||
| results.forEach(function(result) { | ||
| var testCase = session.testCaseLookup[result.id]; | ||
|
|
||
| // While stash.take on the server is supposed to honor read-once, since we're | ||
| // polling so frequently it is possible that we will receive the same test result | ||
| // more than once. | ||
| if (!testCase.done) { | ||
| testCase.done = true; | ||
| session.doneCount++; | ||
| } | ||
|
|
||
| // Validate that the sendbeacon() was actually sent to the server. | ||
| var test = testCase.test; | ||
| test.step(function() { | ||
| // null JSON values parse as null, not undefined | ||
| assert_equals(result.error, null, "'sendbeacon' data must not fail validation"); | ||
| }); | ||
|
|
||
| test.done(); | ||
| }); | ||
|
|
||
| // Continue polling until all of the results come in. | ||
| if (session.doneCount < session.sentCount) { | ||
| // testharness.js frowns upon the use of explicit timeouts, but there is no way | ||
| // around the need to poll for these tests, and there is no use spamming the server | ||
| // with requestAnimationFrame() just to avoid the use of step_timeout. | ||
| step_timeout(waitForResults.bind(this, session), 100); | ||
| } | ||
| }).catch(function(error) { | ||
| failSession(session, `unexpected error reading response, error='${error}'`); | ||
| }); | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| // Fails all of the tests in the session, meant to be called when an infrastructural | ||
| // issue prevents us from deterministically completing the individual tests. | ||
| function failSession(session, reason) { | ||
| session.testCases.forEach(function(testCase) { | ||
| var test = testCase.test; | ||
| test.unreached_func(reason)(); | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>W3C Beacon CORS Worker Test</title> | ||
| <script src="/resources/testharness.js"></script> | ||
| <script src="/resources/testharnessreport.js"></script> | ||
| </head> | ||
| <body> | ||
| <script> | ||
| "use strict"; | ||
|
|
||
| fetch_tests_from_worker(new Worker("beacon-cors.js?pipe=sub")); | ||
| </script> | ||
| </body> | ||
| </html> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, we did not spec a max limit in the spec.. should we be testing it here?
One solution is that we spec 64kb. Another is that we either remove this test, or (better), pump up the value to some "obviously bad" value and test that it fails? E.g. 1MB or some such.