cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

test262 & conformance

Read a real test262 test, from its metadata block to assert.sameValue and negative tests, run it in strict and non-strict mode, and report engine bugs well.

By the end, you can
  • 01
    Read a test262 testExplain each part of a test file: the metadata block, includes, assert.sameValue, assert.throws, and negative.
  • 02
    Predict how a runner runs itSay which modes a test runs in from its flags, and whether each run passes.
  • 03
    Report an engine bug wellShrink a strange result to a tiny test, check it against the spec, and write a clear bug report.

One shared set of tests for every engine

A JavaScript engine is the program that runs your JavaScript. Chrome and Node.js use V8, Firefox uses SpiderMonkey, and Safari uses JavaScriptCore. All of them follow the same rulebook, the ECMAScript specification, or spec for short.

But how do we know that every engine really follows it? Each engine is checked against one shared set of tests, called test262. TC39, the group that writes the spec, keeps these tests next to the spec. There are tens of thousands of them, and each one is a small JavaScript file that checks one rule.

A test passes when it runs to the end without throwing an error. It fails when it throws. Here is the core of a tiny test:

Two checks on a small arrayPop out in the code editor (opens in a new tab)JavaScript
const cart = [3, 4, 5];
assert.sameValue(cart.at(-1), 5);
assert.sameValue(cart.at(0), 3);
console.log("both checks passed");

Line 1 makes an array called cart with three prices: 3, 4 and 5.

Line 2 checks one rule about at(). The at() method reads an item by its position. A negative position counts back from the end, so cart.at(-1) is the last item, 5. assert.sameValue takes two values: the value the code gave, and the value the spec says it should give. If they are the same, it does nothing. If they differ, it throws an error.

Line 3 checks that cart.at(0) is the first item, 3. Both checks pass, so nothing is thrown, and line 4 prints "both checks passed". Real tests don't print anything. Line 4 is only there so you can see that the program reached the end.

assert.sameValue isn't part of JavaScript. It comes from test262's helper files. In this lesson, the editor first loads a small model of those helpers, written for the lesson, so you can run every example. You'll read that model in the harness section below.

One test checks that cart.at(-1) gives 5. Arrows send the same test to three browsers: Chrome with V8, Firefox with SpiderMonkey, and Safari with JavaScriptCore. All three pass.assert.sameValue(cart.at(-1), 5)one testcart.at(-1) is 5ChromeV8passFirefoxSpiderMonkeypassSafariJavaScriptCorepass
The same test runs in every engine. Each one checks that cart.at(-1) gives 5. Here all three engines pass.

Now see what a failing check looks like. This example expects the wrong value on purpose.

A check that expects the wrong valuePop out in the code editor (opens in a new tab)JavaScript
const cart = [3, 4, 5];
try {
  assert.sameValue(cart.at(-1), 4, "at(-1) reads the last item.");
} catch (error) {
  console.log(String(error));
}

Line 1 makes the same cart. Line 3 expects cart.at(-1) to be 4, which is wrong: it is 5. The third value on line 3 is a message that says what the check is about.

So assert.sameValue throws a Test262Error, the error type test262 uses for a failed check. Lines 2 and 4 catch the error, so the program doesn't stop, and line 5 prints it: Test262Error: at(-1) reads the last item. Expected SameValue(«5», «4») to be true. The marks « and » wrap each value: first the value the code gave, then the value the test expected.

Real-life analogyOne exam paper for every school

A country gives the same exam paper to students at every school. Every teacher marks it with the same answer key. So a mark from one school means the same thing as a mark from another school. A red mark on a question shows exactly where a student and the answer key disagree.

In real life: The answer key every teacher uses
In JavaScript: The ECMAScript spec
In real life: The same exam paper, sent to every school
In JavaScript: The test262 tests, run in every engine
In real life: One question on the paper
In JavaScript: One test file, such as the at(-1) check
In real life: A wrong answer gets a red mark
In JavaScript: A check that fails throws a Test262Error
The main idea

test262 is the official set of tests for the ECMAScript spec. Each test is a small JavaScript file that checks one rule. It passes if it runs without throwing, and fails if a check throws. Every major engine runs the same tests, so a passing test means that engine follows that rule the way the spec says.

What the test262 suite holds

A set of tests that belong together is called a test suite. test262 is a project on GitHub, at github.com/tc39/test262. Here are its main folders.

The main folders of the test262 projectText
test262/
  INTERPRETING.md   how a runner must run each test
  harness/          assert.js, sta.js, propertyHelper.js, ...
  test/
    built-ins/      Array, Promise, JSON, Temporal, ...
    language/       statements, expressions, literals, ...
    annexB/         old web features from Annex B
    intl402/        Intl, from the separate ECMA-402 spec
    staging/        new tests that are still being checked

Line 1 is the project folder. Line 2 is INTERPRETING.md, the rules for a runner. A runner is the program that runs each test in an engine and records whether it passed. The rules say which helper files to load and how many times to run each test.

Line 3 is harness/, the folder of helper files, such as assert.js. Line 4 is test/, which holds the tests themselves. Lines 5 to 9 are the folders inside test/.

The table below lists what each folder is for. Read each row as: "the tests in this folder check this."

What each test262 folder holds
Which folderWhat its files are for
harness/Helper files that tests load, such as assert.js, sta.js, and propertyHelper.js.
test/built-ins/Built-in objects and functions, such as Array, Promise, and JSON.
test/language/The syntax and what it does: statements, expressions, literals, and more.
test/annexB/Old web features the spec keeps in Annex B, such as escape() and substr().
test/intl402/The Intl objects, from the separate ECMA-402 spec.
test/staging/New tests that are still being checked before they move into the main folders.

Each folder splits further, down to one feature. For example, the tests for at() on arrays sit in test/built-ins/Array/prototype/at/. Each file in that folder checks one rule about at().

Many people write tests: the people who propose a new feature, engine teams, and volunteers. A new feature needs test262 tests before it can join the spec, so the tests are ready before most people can use the feature.

Reading a test: the metadata block

A real test file has two parts. First comes a block of facts about the test, called metadata. Then comes the test code. Here is a whole test file for at().

A whole test file for at()Pop out in the code editor (opens in a new tab)JavaScript
// Copyright (C) 2026 Asha. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.at
description: at() counts back from the end for a negative index
info: |
  Let relativeIndex be ? ToIntegerOrInfinity(index).
  If relativeIndex >= 0, let k be relativeIndex.
  Else, let k be len + relativeIndex.
features: [Array.prototype.at]
---*/

var cart = [3, 4, 5];
assert.sameValue(cart.at(-1), 5, "at(-1) reads the last item");
assert.sameValue(cart.at(-3), 3, "at(-3) reads the first item");
assert.sameValue(cart.at(-4), undefined, "at(-4) is before the first item");

Lines 1 and 2 are the copyright note that every test file starts with. The name and year change from file to file.

Line 3, /*---, opens the metadata block, and line 11, ---*/, closes it. The block is inside a comment, so the engine skips it. The runner reads it to decide how to run the test. The block uses YAML, a simple format where each line is a name, a colon, and a value.

  • Line 4, esid, names the spec section this test checks. sec-array.prototype.at is the id at the end of that section's web address.
  • Line 5, description, says in one line what the test checks.
  • Line 6, info, holds longer notes. The | means "the next lines all belong here". Here, lines 7 to 9 copy the spec steps the test is about.
  • Line 10, features, lists the features the test needs. A runner can skip the test in an engine that doesn't have Array.prototype.at yet.

Lines 7 to 9 are spec steps. Spec steps read like a recipe. "Let x be …" means "call the result x". A ? means "if this step goes wrong, stop and throw the error". In plain words, line by line:

  1. Turn the position you passed in into a whole number. ToIntegerOrInfinity drops any part after the decimal point. Call the result relativeIndex.
  2. If it is 0 or more, use it as the position k.
  3. Otherwise, count back from the end: add it to len, the length of the array.

So for at(-1) on a 3-item array, k is 3 + -1 = 2, the last position.

Lines 13 to 16 are the test code. Line 13 makes the same cart as before. Line 14 checks that at(-1) gives the last item, 5. Line 15 checks that at(-3) gives the first item, 3: 3 + -3 = 0. Line 16 checks that at(-4) gives undefined: 3 + -4 = -1, which is before the first item, so there is nothing to read. Each check has a message, so a failure says which rule broke.

The file at.js holds two parts. Lines 3 to 11 are the metadata, which the runner reads. Lines 13 to 16 are the test code, which the engine runs.at.jslines 3 to 11: metadatalines 13 to 16: test codethe runnerreads itthe engineruns it
Lines 3 to 11 sit inside a comment, so the engine skips them. The runner reads them to decide how to run the test. Lines 13 to 16 are the test code the engine runs.

The table below lists the metadata names you'll see most. Read each row as: "when a test has this name in its metadata, it tells the runner this."

The common names in a test262 metadata block
Which nameWhat it tells the runner
esidThe spec section the test checks, by the id in that section's web address.
descriptionOne line that says what the test checks.
infoLonger notes, often the spec steps the test is about.
featuresThe features the test needs. A runner can skip tests for features an engine doesn't have yet.
includesExtra harness files to load before the test, such as propertyHelper.js.
flagsHow to run the test: only strict, only non-strict, as a module, async, or raw.
negativeThe test must throw. phase says when, and type says which error.

You'll meet includes, negative and flags in the next three sections.

The harness: assert.sameValue, assert.throws and includes

The harness is the set of helper files a runner loads before each test. Two of them are loaded for almost every test:

  • sta.js makes Test262Error, the error a failed check throws, and $DONOTEVALUATE, which you'll meet in the next section.
  • assert.js makes assert and its checks, such as assert.sameValue and assert.throws.

The real files have more checks and handle more cases. The model below is small and was written for this lesson, but it works the same way for every example here. The editor loads it before each example, so you don't run it yourself.

A small model of sta.js and assert.js, written for this lessonJavaScript
function Test262Error(message) {
  this.message = message || "";
}
Test262Error.prototype.toString = function () {
  return "Test262Error: " + this.message;
};
function $DONOTEVALUATE() {
  throw "Test262: This statement should not be evaluated.";
}
function assert(mustBeTrue, message) {
  if (mustBeTrue !== true) throw new Test262Error(message);
}
assert._format = function (value) {
  if (Object.is(value, -0)) return "-0";
  return typeof value === "string" ? JSON.stringify(value) : String(value);
};
assert.sameValue = function (actual, expected, message) {
  if (Object.is(actual, expected)) return;
  const reason = "Expected SameValue(«" + assert._format(actual) + "», «" + assert._format(expected) + "») to be true";
  throw new Test262Error(message ? message + " " + reason : reason);
};
assert.throws = function (expectedError, func) {
  try {
    func();
  } catch (thrown) {
    if (thrown?.constructor === expectedError) return;
    throw new Test262Error("Expected a " + expectedError.name + " but got a " + thrown?.constructor?.name);
  }
  throw new Test262Error("Expected a " + expectedError.name + " to be thrown but no exception was thrown at all");
};
  • Lines 1 to 3 make Test262Error. Line 2 keeps the message it is given.
  • Lines 4 to 6 say how the error turns into text: "Test262Error: " and then the message. That is the text the examples print.
  • Lines 7 to 9 make $DONOTEVALUATE. Line 8 throws a plain string as soon as it is called.
  • Lines 10 to 12 make assert itself. Line 11 throws a Test262Error unless the value is exactly true.
  • Lines 13 to 16 make a helper that turns a value into text for a message. Line 14 shows -0 as "-0", because plain String(-0) gives "0". Line 15 puts quotes around text.
  • Lines 17 to 21 make assert.sameValue. Line 18 returns quietly if the two values are the same. Line 19 builds the message, and line 20 throws it.
  • Lines 22 to 30 make assert.throws. Line 24 calls the function it is given. If that throws, line 26 checks which constructor made the error, and line 27 fails if it is the wrong one. Line 29 fails if nothing was thrown at all.

Line 18 uses Object.is, which follows the spec's SameValue rule. It is almost the same as ===, with two differences: NaN is the same as NaN, and -0 is not the same as 0. Test262 needs this, because the spec often cares about -0.

assert.sameValue compares like Object.isPop out in the code editor (opens in a new tab)JavaScript
assert.sameValue(NaN, NaN);
assert.sameValue(0.1 + 0.2 === 0.3, false);
try {
  assert.sameValue(-0, 0);
} catch (error) {
  console.log(String(error));
}

Line 1 compares NaN with NaN. NaN === NaN is false, but under SameValue they are the same, so the check passes and prints nothing.

Line 2 shows a common way to check a true or false result. 0.1 + 0.2 is 0.30000000000000004, so 0.1 + 0.2 === 0.3 is false. The test expects false, so this check passes too.

Line 4 compares -0 with 0. -0 === 0 is true, but under SameValue they differ, so the check throws. Lines 5 and 6 catch the error and print Test262Error: Expected SameValue(«-0», «0») to be true.

The table below compares the two rules. Read each row as: "when I compare these two values, === says this, and assert.sameValue does this."

=== and assert.sameValue disagree on NaN and -0
When you compareWhat === saysWhat assert.sameValue does
NaN and NaNNaN === NaN is false.assert.sameValue(NaN, NaN) passes.
-0 and 0-0 === 0 is true.assert.sameValue(-0, 0) fails.
3 and "3"3 === "3" is false.assert.sameValue(3, "3") fails.

Some rules in the spec say "throw a TypeError here". assert.throws checks rules like that. You give it the error you expect and a function. It calls the function and checks that it throws that kind of error.

assert.throws checks which error a function throwsPop out in the code editor (opens in a new tab)JavaScript
assert.throws(TypeError, function () {
  null.price;
});
try {
  assert.throws(TypeError, function () {
    return [3, 4].at(5);
  });
} catch (error) {
  console.log(String(error));
}

Line 1 expects a TypeError. Line 2 reads a property of null, which throws a TypeError. That is the error line 1 asked for, so the check passes and prints nothing.

Line 5 expects a TypeError too. But line 6 reads [3, 4].at(5), which is past the end of the array. at() doesn't throw for that. It just gives undefined. No error comes out, so assert.throws throws its own Test262Error. Lines 8 and 9 catch it and print Test262Error: Expected a TypeError to be thrown but no exception was thrown at all.

Step through two assert.throws checks
Step 0 of 8Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. The checks run for real in the lesson's small model of assert.js.

Running in
  1. script
Next: line 1
  null.price;});try {  assert.throws(TypeError, function () {    return [3, 4].at(5);  });} catch (error) {  console.log(String(error));}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step to walk through both checks. Watch the second one: the function returns undefined, and the failure comes from assert.throws, not from at(). The checks run for real in the lesson's small model of assert.js. This replays the example code; it isn't a view inside the JavaScript engine.

Some tests need more helpers than assert.js has. They ask for them with includes in the metadata block. This test checks how the at property is stored on Array.prototype, the object every array gets its methods from.

A test that loads an extra helper fileJavaScript
/*---
esid: sec-array.prototype.at
description: at is writable and configurable, but not enumerable
includes: [propertyHelper.js]
features: [Array.prototype.at]
---*/

verifyProperty(Array.prototype, "at", {
  writable: true,
  enumerable: false,
  configurable: true,
});

Lines 1 to 6 are the metadata block. Line 4 lists propertyHelper.js under includes. That file makes verifyProperty, a check for the settings of one property.

Lines 8 to 12 call verifyProperty. Line 8 names the object and the property: at on Array.prototype. Lines 9 to 11 list three settings every property has:

  • Line 9, writable: true: code can change the property's value.
  • Line 10, enumerable: false: the property doesn't show up in a for...in loop.
  • Line 11, configurable: true: code can delete the property or change its settings.

This example can't run here, because the lesson's model doesn't include propertyHelper.js.

One run of the test, as a stack of four files in order: 1 assert.js and 2 sta.js, which are always loaded, 3 propertyHelper.js, which comes from includes, and 4 the test file itself.one run1. assert.js2. sta.js3. propertyHelper.js4. the test filealways loadedfrom includesthe test itself
Before the test, the runner loads assert.js and sta.js, then each file listed under includes. They all run together, so line 8 of the test can call verifyProperty.

You can read the same three settings yourself, in plain JavaScript:

Read the settings of Array.prototype.atPop out in the code editor (opens in a new tab)JavaScript
const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, "at");
console.log(descriptor.writable, descriptor.enumerable, descriptor.configurable);

Line 1 calls Object.getOwnPropertyDescriptor, which gives back an object that describes one property: its value and its settings. Line 2 prints the three settings: true false true. They match lines 9 to 11 of the test, so that test passes in this browser.

Negative tests: code that must fail

The spec doesn't only say what code does. It also says which code is not allowed at all. Some mistakes must be found while the engine reads the code, before any of it runs. The spec calls these early errors. They are thrown as a SyntaxError.

A negative test checks that the engine rejects such code. It passes only if the right error is thrown at the right time.

A negative test: the same let name twice in one blockJavaScript
/*---
esid: sec-block-static-semantics-early-errors
description: A block can't declare the same let name twice
negative:
  phase: parse
  type: SyntaxError
---*/

$DONOTEVALUATE();

{
  let price = 3;
  let price = 4;
}

Lines 1 to 7 are the metadata block. Lines 4 to 6 make this a negative test:

  • Line 4, negative, says the test must throw.
  • Line 5, phase: parse, says when: while the engine reads the code, before anything runs.
  • Line 6, type: SyntaxError, says which error.

Lines 11 to 14 are the code that must be rejected: a block that declares let price twice. The spec forbids that, so the engine must throw a SyntaxError.

Line 9 calls $DONOTEVALUATE(), which throws as soon as it runs. In a correct engine, it never runs, because the whole file is rejected first. But if an engine wrongly accepts the code, line 9 is the first line to run. It throws a plain string, not a SyntaxError, so the test fails.

The table below lists the three phases a negative test can name. Read each row as: "when a test says this phase, the error must happen at this time."

The phases a negative test can name
Which phaseWhen the error must happen
parseBefore any code runs, while the engine reads the test.
resolutionWhile a module test loads the modules it imports.
runtimeWhile the test runs.

This example can't run as written: the editor would just show the SyntaxError. To run it, you need a runner that reads the metadata and checks the error. Here is a tiny model of a runner, written for this lesson. Like the harness model, the editor loads it before each example.

A tiny model of a test262 runner, written for this lessonJavaScript
function runOnce(code) {
  let phase = "parse";
  try {
    const test = new Function(code);
    phase = "runtime";
    test();
    return { phase: null, error: null };
  } catch (error) {
    return { phase, error };
  }
}
function runTest(name, source, meta = {}) {
  const flags = meta.flags ?? [];
  let modes = ["non-strict", "strict"];
  if (flags.includes("onlyStrict")) modes = ["strict"];
  if (flags.includes("noStrict")) modes = ["non-strict"];
  for (const mode of modes) {
    const code = mode === "strict" ? '"use strict";\n' + source : source;
    const { phase, error } = runOnce(code);
    const negative = meta.negative;
    const passed = negative
      ? phase === negative.phase && error?.constructor?.name === negative.type
      : error === null;
    console.log(name + " (" + mode + "): " + (passed ? "pass" : "fail, " + (error ?? "no error")));
  }
}

Lines 1 to 11 make runOnce, which runs one piece of code and reports what happened:

  • Line 2 sets phase to "parse", because the engine reads the code first.
  • Line 4 turns the text into a function with new Function. This is where the engine reads the code. An early error is thrown here, while phase is still "parse".
  • Line 5 sets phase to "runtime", and line 6 runs the code.
  • Line 7 reports no error. Line 9 reports the phase and the error, if anything was thrown.

Lines 12 to 26 make runTest, which runs a whole test:

  • Lines 13 to 16 decide which modes to run. You'll use them in the flags section.
  • Line 17 loops over the modes, and line 18 puts "use strict" on top for the strict run.
  • Line 19 runs the code once.
  • Lines 20 to 23 decide whether the run passed. A negative test passes when the phase and the error type both match. Any other test passes when nothing was thrown.
  • Line 24 prints one line per run: the test name, the mode, and "pass" or "fail" with the error.
Run the negative test in the model runnerPop out in the code editor (opens in a new tab)JavaScript
const source = `$DONOTEVALUATE();
{
  let price = 3;
  let price = 4;
}`;
runTest("duplicate let", source, {
  negative: { phase: "parse", type: "SyntaxError" },
});

Lines 1 to 5 keep the test code as text, between backticks. It is lines 9 to 14 of the test file, without the empty lines. Lines 6 to 8 call runTest and pass the same facts as the metadata block: phase "parse" and type "SyntaxError".

With no flags, the runner runs the test twice. Both times, the engine rejects the two let price lines while reading, so $DONOTEVALUATE() on line 1 never runs. The program prints duplicate let (non-strict): pass and then duplicate let (strict): pass.

Flags: strict, non-strict, module and async runs

JavaScript has two modes. Writing "use strict" on the first line of a script turns on strict mode. Strict mode turns some old, error-prone features into errors, and changes a few rules. Code without it runs in non-strict mode, often called sloppy mode.

The spec describes both modes, so a test must pass in both, unless it says otherwise. It says otherwise with flags in its metadata block. The table below lists the flags that change how a test runs. Read each row as: "when a test lists this flag, the runner runs it like this."

How each flag changes the way a runner runs a test
When the test listsHow the runner runs it
No flagsTwice: once as written, in non-strict mode, and once with "use strict" put on top.
onlyStrictOnce, with "use strict" put on top.
noStrictOnce, as written. Strict mode is never used.
moduleOnce, as a module. Module code is always strict, so nothing is added.
asyncThe test calls $DONE() when it finishes. The runner waits for that call.
rawOnce, exactly as written: no harness files and no "use strict".

Here is a test that only works in one of the two modes. It runs in the model runner twice: once with no flags, and once with onlyStrict.

One test, run with no flags and with onlyStrictPop out in the code editor (opens in a new tab)JavaScript
const source = `function kind() {
  return typeof this;
}
assert.sameValue(kind(), "undefined");`;
runTest("no flags", source);
runTest("onlyStrict", source, { flags: ["onlyStrict"] });

Lines 1 to 4 keep the test as text. Lines 1 to 3 make kind, which returns typeof this: the type of the value this holds. Line 4 calls kind() on its own, not as a method of an object, and expects "undefined".

Line 5 runs the test with no flags, so it runs twice:

  • In the non-strict run, a call on its own gets the global object as this. So kind() gives "object", and the check fails: no flags (non-strict): fail, Test262Error: Expected SameValue(«"object"», «"undefined"») to be true.
  • In the strict run, a call on its own gets undefined as this. So the check passes: no flags (strict): pass.

Line 6 runs the same test with onlyStrict. The runner skips the non-strict run, so the only line is onlyStrict (strict): pass. The test itself was fine. It only needed the right flag.

The test with no flags runs twice. The non-strict run uses it as written: kind() gives object, and the run fails. The strict run puts use strict on top: kind() gives undefined, and the run passes.the testno flagsnon-strictkind() gives "object"failstrictkind() gives "undefined"pass
With no flags, the runner runs the test twice. As written, kind() gives "object" and the check fails. With "use strict" on top, it gives "undefined" and the check passes.
Step through one test in two modes
Step 0 of 14Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. The test runs for real in the lesson's small model runner, once for each mode.

Running in
  1. script
Next: line 1
  return typeof this;}assert.sameValue(kind(), "undefined");`;runTest("no flags", source);runTest("onlyStrict", source, { flags: ["onlyStrict"] });
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Step through lines 5 and 6. Watch how many runs each call plans, and where "use strict" is put on top. The test runs for real in the lesson's small model runner. This replays the example code; it isn't a view inside the JavaScript engine.

Now pick the flags yourself. The playground has four tests:

  • at(-1) reads the last item. It works in both modes.
  • this in a plain call is the test from above.
  • old octal 010 uses 010, an old way to write the number 8 with a leading zero. Strict mode doesn't allow it.
  • duplicate let is the negative test from the last section.

For each test, find the flags where every run passes, and no mode is left out that the test could cover.

Playground: pick the flags for a test
The test file, with the flags you pickedJavaScript
/*---description: A plain call gets undefined as this---*/ function kind() {  return typeof this;}assert.sameValue(kind(), "undefined");
Runner output1 of 2 runs pass
non-strict runfail, Test262Error: Expected SameValue(«"object"», «"undefined"») to be true
strict runpass
Try it yourself
The flags decide which runs happen.

A run failed, so these flags are wrong for this test. Try onlyStrict.

Each run is real: the test runs in this page through the lesson's small model runner, once for each mode its flags allow.

Two more flags change how a test runs. The module flag runs the test as a module, the kind of file that can use import and export.

A test that runs as a moduleJavaScript
/*---
esid: sec-module-semantics
description: Code in a module is always strict
flags: [module]
---*/

function kind() {
  return typeof this;
}
assert.sameValue(kind(), "undefined");

Line 4 lists the module flag. Lines 7 to 10 are the same check as before: a call on its own must get undefined as this. Code in a module is always strict, so this test passes without "use strict". The runner runs it once, as a module, and adds nothing.

The async flag is for tests that finish later, such as tests of promises. A promise is an object for a value that may arrive later.

An async test calls $DONE when it finishesJavaScript
/*---
esid: sec-promise.resolve
description: Promise.resolve gives a promise that settles with the value
flags: [async]
---*/

Promise.resolve(3).then(function (price) {
  assert.sameValue(price, 3);
}).then($DONE, $DONE);

Line 4 lists the async flag, so the runner also loads a harness file that makes $DONE, and waits for it to be called. Line 7 makes a promise that settles with 3, and asks for a function to run when it does. Line 8 checks the value.

Line 9 calls $DONE when the check is finished. If the check passed, $DONE is called with no error, and the test passes. If the check threw, the second $DONE gets the error, and the test fails. If $DONE is never called, the test fails too.

Why engines get different results

An engine that follows the spec is said to conform to it. Conformance is how closely it does. In practice, no engine passes every test262 test, and that's normal. The failures have different causes.

The first cause is features that aren't shipped yet. A runner reads the features list to skip those tests. This example makes the same kind of check in your browser:

Which features does this browser have?Pop out in the code editor (opens in a new tab)JavaScript
const features = {
  "Array.prototype.at": typeof Array.prototype.at === "function",
  "array-grouping": typeof Object.groupBy === "function",
  Temporal: typeof globalThis.Temporal === "object",
};
for (const [name, ready] of Object.entries(features)) {
  console.log(name + ": " + (ready ? "run its tests" : "skip its tests"));
}

Lines 1 to 5 make an object with one entry per feature. Each name is the one test262 uses in its features lists. Each value is true if the feature is there:

  • Line 2 checks that arrays have an at method.
  • Line 3 checks for Object.groupBy. Test262 lists it under the name "array-grouping".
  • Line 4 checks for Temporal, the newer way to work with dates and times.

Lines 6 to 8 print one line per feature. The output depends on your browser. In a browser without Temporal, the last line is "Temporal: skip its tests".

The table below lists the common reasons a test fails in an engine. Read each row as: "when a test fails for this reason, it means this."

Why a test262 test can fail in an engine
Why the test failsWhat that means
The feature isn't shipped yetTests that list that name under features fail or are skipped. This is normal for new features.
Missing language dataSome intl402 tests need data for many languages. A small build of an engine may leave that data out.
A recent spec changeThe spec changed, and the tests changed with it. The engine hasn't caught up yet.
An engine bugThe engine does something the spec doesn't allow. This is what bug reports are for.
A wrong testTests are written by people too. A test that disagrees with the spec gets fixed in test262.

Engine teams run test262 often, and each keeps a list of the tests it is known to fail. So when a change makes a new test fail, the team sees it at once. Some websites, such as test262.fyi, run the whole suite in many engines and show how many tests each one passes.

A higher score doesn't make one engine better for your page. What matters is whether the features you use work the same everywhere you need them.

Reporting an engine bug with a small test

Now and then, a program gives a result that looks impossible, and it's tempting to blame the engine. Here is a small app that seems to read the wrong price.

An app that reads the wrong pricePop out in the code editor (opens in a new tab)JavaScript
const cart = [3, 4, 5];
const step = 0.5;
function lastPrice(items) {
  return items.at(-step);
}
console.log(lastPrice(cart));

Line 1 makes the cart of prices. Line 2 sets step to 0.5. Lines 3 to 5 make lastPrice, which should read the last item: line 4 calls items.at(-step), which is items.at(-0.5). Line 6 prints 3, not the last price, 5.

Before you report a bug, work through four steps.

Step 1: shrink it. Cut the code down to the smallest piece that still shows the strange result. Leave out your libraries, because they can change built-in objects. The smallest code that still shows a problem is called a reproduction.

The same result in two linesPop out in the code editor (opens in a new tab)JavaScript
console.log([3, 4, 5].at(-0.5));
console.log([3, 4, 5].at(-1));

Line 1 prints 3: at(-0.5) reads the first item. Line 2 prints 5: at(-1) reads the last item. So the strange result comes from the -0.5.

Step 2: check the spec. Look up the steps for at(), the same steps as lines 7 to 9 of the test file earlier. Step 3 of the spec's at() uses ToIntegerOrInfinity, which drops the part after the decimal point. So -0.5 becomes 0. 0 is not below 0, so the position is 0: the first item, 3.

So the engine is right, and the bug is in the app: line 2 should set step to 1. Most strange results end here. The spec check takes a few minutes and saves everyone time.

Step 3: write it as a test. A tiny test in test262 style makes the expected result clear, and you can run it in any engine.

The reproduction as a test262-style testPop out in the code editor (opens in a new tab)JavaScript
/*---
esid: sec-array.prototype.at
description: at() turns -0.5 into 0, so it reads the first item
features: [Array.prototype.at]
---*/

assert.sameValue([3, 4, 5].at(-0.5), 3);

Lines 1 to 5 are the metadata block: the spec section, a one-line description, and the feature it needs. Line 7 checks that [3, 4, 5].at(-0.5) is 3. Run it: it prints nothing, because the check passes in this browser.

Step 4: report it. If an engine did print 5 for that line, it would break the spec, and that would be a real engine bug. Here is what a good report would say.

A clear engine bug reportText
Title: [3, 4, 5].at(-0.5) gives 5, but the spec says 3
Engine: the engine's name and exact version
Code: console.log([3, 4, 5].at(-0.5));
Expected: 3
Actual: 5
Spec: Array.prototype.at, step 3 (ToIntegerOrInfinity turns -0.5 into 0)
test262: the test that fails, or a new test like the one above
  • Line 1, the title, says what happens and what the spec says, in one line.
  • Line 2 names the engine and its exact version. The bug may already be fixed in a newer one.
  • Line 3 is the reproduction: one line anyone can run.
  • Lines 4 and 5 give the expected and the actual output, so the difference is plain.
  • Line 6 points at the spec step that decides the answer.
  • Line 7 names the test262 test that fails, or offers a new one like the test above.

Each engine has a public bug tracker: the Chromium issue tracker for V8, Mozilla's Bugzilla for SpiderMonkey, and WebKit's Bugzilla for JavaScriptCore. If you think a test262 test is wrong, open an issue in the test262 project on GitHub instead.

Real-life analogyTelling a mechanic about a strange noise

"My car makes a noise" doesn't help a mechanic much. "My 2019 car clicks when I turn left, and only then" does. The mechanic can hear it in one short drive, and knows where to look.

In real life: Say the exact car, model and year
In JavaScript: Give the engine's name and exact version
In real life: Show when the noise happens: only when you turn left
In JavaScript: Give the smallest code that shows the problem
In real life: Say what it should sound like, and what it does sound like
In JavaScript: Give the expected and the actual output
In real life: Point at the page of the car's manual
In JavaScript: Point at the spec step
What goes in an engine bug report?
  • The engine's name and exact version
  • The smallest code that still shows the problem
  • What you expected, and what you got
  • The spec step that says what should happen
  • Your whole app, zipped
  • The libraries your app loads
  • "It's broken, please fix" with no code
Try it yourself
0 of 7 correct

Put each item in its group: things a good bug report includes, and things to leave out.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

Most mix-ups about test262 come from expecting too much from a test result, or from reading an assert like plain ===.

  • "An engine that passes test262 has no bugs." No. The tests only check what they cover. An engine can still have bugs in places no test checks.
  • "A failing test always means an engine bug." No. The feature may not be shipped yet, language data may be missing, or the test itself may be wrong.
  • "assert.sameValue compares with ===." No. It uses SameValue, like Object.is: NaN matches NaN, and -0 doesn't match 0.
  • "A negative test is a broken test." No. It checks that the engine rejects code the spec forbids, with the right error at the right time.
  • "Every test runs once." No. A test with no flags runs twice: once as written, and once with "use strict" on top.
  • "The engine with the highest score is the best one for my page." No. A score counts all tests. Check the features your page uses instead.

The table below puts three easily confused ideas side by side. Read each row as: "this term means this, but it doesn't mean that."

What test262, a score, and a failing test do and don't tell you
TermWhat it meansWhat it doesn't mean
test262Checks that an engine follows the spec.It doesn't prove an engine has no bugs. It only checks what its tests cover.
A conformance scoreShows how many tests an engine passes.It doesn't say which engine is best for your page. Look at the features you use.
A failing testShows a place where an engine and a test disagree.It isn't always an engine bug. The feature may be unshipped, or the test may be wrong.

Practice exercises

Each program below uses the lesson's model harness and runner, so assert and runTest are already there. Guess the result before you run it. Then use the hints if you get stuck.

Exercise 1 · Warm-upPredict a check with -0

Two checks run inside a try. If either one throws, result becomes "fail". What does line 8 print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
let result = "pass";
try {
  assert.sameValue([3, 4, 5].at(-1), 5);
  assert.sameValue(0 / -1, 0);
} catch (error) {
  result = "fail";
}
console.log(result);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict which run fails

    Line 1 keeps a one-line test that checks 010 is 8. Line 2 runs it with no flags. Which run fails?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const source = `assert.sameValue(010, 8);`;
    runTest("old octal", source);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the wrong error type

      This check fails and prints Test262Error: Expected a RangeError but got a TypeError. The code on line 4 is right; line 3 expects the wrong error. Which constructor should line 3 name?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      let result = "pass";
      try {
        assert.throws(RangeError, function () {
          return Symbol("tea") + "";
        });
      } catch (error) {
        result = String(error);
      }
      console.log(result);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix the expected value

        Someone wrote this test, and it fails. The engine is right; the expected value is wrong. What should the expected value on line 3 be?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        let result = "pass";
        try {
          assert.sameValue([3, NaN, 5].indexOf(NaN), 1);
        } catch (error) {
          result = String(error);
        }
        console.log(result);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePick the flag for a with test

          A with block lets you use an object's properties as plain names: inside it, price means cart.price. This test runs with no flags. Which flag should it list, so that every run passes?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const source = `var cart = { price: 3 };
          with (cart) {
            assert.sameValue(price, 3);
          }`;
          runTest("with", source);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeFix a negative test with a flag

            This negative test says a function can't have two parameters with the same name. It passes in one mode and fails in the other. Which flag should the options on lines 5 to 7 add?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const source = `$DONOTEVALUATE();
            function add(price, price) {
              return price;
            }`;
            runTest("duplicate parameters", source, {
              negative: { phase: "parse", type: "SyntaxError" },
            });

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask: what does the metadata tell the runner, which runs happen, and does anything throw? Each answer explains why it's right or wrong.

              The test262 quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What does a test262 test print when it passes?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this sameValue check with NaN print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                try {
                  assert.sameValue(NaN, NaN);
                  console.log("pass");
                } catch (error) {
                  console.log("fail");
                }

                Choose an answer to see the explanation.

              3. Question 3 of 8A test lists no flags. How does the runner run it?

                Choose an answer to see the explanation.

              4. Question 4 of 8What does this metadata block tell the runner?

                Read the code, then predictJavaScript
                /*---
                description: A function can't have two parameters with the same name here
                flags: [onlyStrict]
                negative:
                  phase: parse
                  type: SyntaxError
                ---*/

                Choose an answer to see the explanation.

              5. Question 5 of 8What does this assert.throws print when the function doesn't throw?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                try {
                  assert.throws(RangeError, function () {
                    return [3, 4, 5].at(9);
                  });
                } catch (error) {
                  console.log(String(error));
                }

                Choose an answer to see the explanation.

              6. Question 6 of 8Why does a test with the module flag never get "use strict" put on top?

                Choose an answer to see the explanation.

              7. Question 7 of 8An engine fails every test that lists features: [Temporal]. What is the most likely reason?

                Choose an answer to see the explanation.

              8. Question 8 of 8Which of these makes the best engine bug report?

                Choose an answer to see the explanation.

              Key takeaways

              Every engine reads the same spec, and test262 checks that they all read it the same way.

              • test262 is the official set of tests for the ECMAScript spec. Every major engine runs it.
              • A test passes when it runs to the end without throwing. A failed check throws a Test262Error.
              • The metadata block tells the runner how to run the test: esid, features, includes, flags and negative.
              • assert.sameValue compares like Object.is, and assert.throws checks which error a function throws.
              • A negative test must throw a named error in a named phase. $DONOTEVALUATE() makes it fail if the engine wrongly runs the code.
              • With no flags, a test runs twice: as written, and with "use strict" on top. onlyStrict, noStrict, module, async and raw change that.
              • A failing test can mean a feature that isn't shipped, missing data, a recent spec change, an engine bug, or a wrong test.
              • Before you report an engine bug, shrink the code, check the spec, and write a tiny test. Then report the version, the code, and both outputs.

              Remember the one-liner.
              One spec, one shared set of tests: a test passes when it runs without throwing.

              Coming next: Sloppy mode in depth. It looks closely at non-strict mode, the mode that tests with noStrict check, and the old rules it still keeps.

              CompleteFrontend Clear concepts. Working examples.