diff --git a/test/built-ins/RegExp/named-groups/groups-object-subclass-sans.js b/test/built-ins/RegExp/named-groups/groups-object-subclass-sans.js
new file mode 100644
index 0000000000000000000000000000000000000000..8d27ea5052d8b1ab4b34ce1e87bab0b833d7f05d
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/groups-object-subclass-sans.js
@@ -0,0 +1,37 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+  Test the groups object on RegExp subclass results that do not have their own.
+includes: [propertyHelper.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: RegExpBuiltinExec ( R, S )
+    24. If _R_ contains any |GroupName|, then
+      a. Let _groups_ be ObjectCreate(*null*).
+    25. Else,
+      a. Let _groups_ be *undefined*.
+    26. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
+---*/
+
+class FakeRegExp extends RegExp {
+  exec(subject) {
+    const fakeResult = ["ab", "a"];
+    fakeResult.index = 0;
+    // `groups` is not set, triggering prototype lookup.
+    return fakeResult;
+  }
+};
+
+const re = new FakeRegExp();
+const result = re.exec("ab");
+assert.sameValue(result.__proto__, Array.prototype);
+assert.sameValue(false, result.hasOwnProperty("groups"));
+
+Array.prototype.groups = { a: "b" };
+Array.prototype.groups.__proto__.b = "c";
+assert.sameValue("b", "ab".replace(re, "$<a>"));
+assert.sameValue("c", "ab".replace(re, "$<b>"));
+Array.prototype.groups = undefined;
diff --git a/test/built-ins/RegExp/named-groups/groups-object-subclass.js b/test/built-ins/RegExp/named-groups/groups-object-subclass.js
new file mode 100644
index 0000000000000000000000000000000000000000..a7880586af18aaea501a99291e21e8ea863ce48d
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/groups-object-subclass.js
@@ -0,0 +1,35 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+  Test the groups object on RegExp subclass results that have their own.
+includes: [propertyHelper.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: RegExpBuiltinExec ( R, S )
+    24. If _R_ contains any |GroupName|, then
+      a. Let _groups_ be ObjectCreate(*null*).
+    25. Else,
+      a. Let _groups_ be *undefined*.
+    26. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
+---*/
+
+class FakeRegExp extends RegExp {
+  exec(subject) {
+    const fakeResult = ["ab", "a"];
+    fakeResult.index = 0;
+    fakeResult.groups = { a: "b" };
+    fakeResult.groups.__proto__.b = "c";
+    return fakeResult;
+  }
+};
+
+const re = new FakeRegExp();
+const result = re.exec("ab");
+assert.sameValue(result.__proto__, Array.prototype);
+assert(result.hasOwnProperty("groups"));
+assert.sameValue("b", result.groups.a);
+assert.sameValue("b", "ab".replace(re, "$<a>"));
+assert.sameValue("c", "ab".replace(re, "$<b>"));
diff --git a/test/built-ins/RegExp/named-groups/groups-object-undefined.js b/test/built-ins/RegExp/named-groups/groups-object-undefined.js
new file mode 100644
index 0000000000000000000000000000000000000000..edeeb2a622d7a04943e0c348c09a47d8413aae5c
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/groups-object-undefined.js
@@ -0,0 +1,33 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The groups object is created unconditionally.
+includes: [propertyHelper.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: RegExpBuiltinExec ( R, S )
+    24. If _R_ contains any |GroupName|, then
+      a. Let _groups_ be ObjectCreate(*null*).
+    25. Else,
+      a. Let _groups_ be *undefined*.
+    26. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
+---*/
+
+const re = /./;
+const result = re.exec("a");
+assert.sameValue(result.__proto__, Array.prototype);
+assert(result.hasOwnProperty("groups"));
+assert.sameValue("a", result[0]);
+assert.sameValue(0, result.index);
+assert.sameValue(undefined, result.groups);
+verifyProperty(result, "groups", {
+  writable: true,
+  enumerable: true,
+  configurable: true,
+});
+
+Array.prototype.groups = { a: "b" };
+assert.sameValue("$<a>", "a".replace(re, "$<a>"));
+Array.prototype.groups = undefined;
diff --git a/test/built-ins/RegExp/named-groups/groups-object-unmatched.js b/test/built-ins/RegExp/named-groups/groups-object-unmatched.js
new file mode 100644
index 0000000000000000000000000000000000000000..34615d3aeb1ee0a2a519c23b34438b362546e57c
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/groups-object-unmatched.js
@@ -0,0 +1,36 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+  Test the groups object with matched and unmatched named captures.
+includes: [propertyHelper.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: RegExpBuiltinExec ( R, S )
+    24. If _R_ contains any |GroupName|, then
+      a. Let _groups_ be ObjectCreate(*null*).
+    25. Else,
+      a. Let _groups_ be *undefined*.
+    26. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
+---*/
+
+const re = /(?<a>a).|(?<x>x)/;
+const result = re.exec("ab");
+assert.sameValue(result.__proto__, Array.prototype);
+assert(result.hasOwnProperty("groups"));
+assert.sameValue("ab", result[0]);
+assert.sameValue("a", result[1]);
+assert.sameValue(undefined, result[2]);
+assert.sameValue(0, result.index);
+assert.sameValue("a", result.groups.a);
+assert.sameValue(undefined, result.groups.x);
+
+// `a` is a matched named capture, `b` is an unmatched named capture, and `z`
+// is not a named capture.
+Array.prototype.groups = { a: "b", x: "y", z: "z" };
+assert.sameValue("a", "ab".replace(re, "$<a>"));
+assert.sameValue("", "ab".replace(re, "$<x>"));
+assert.sameValue("", "ab".replace(re, "$<z>"));
+Array.prototype.groups = undefined;
diff --git a/test/built-ins/RegExp/named-groups/groups-object.js b/test/built-ins/RegExp/named-groups/groups-object.js
index a7d2b5433ccb991f36de43a8042cd5b893b0ed27..d511e8b73a2865a3584a3aedac3656a24417f932 100644
--- a/test/built-ins/RegExp/named-groups/groups-object.js
+++ b/test/built-ins/RegExp/named-groups/groups-object.js
@@ -8,26 +8,31 @@ esid: sec-regexpbuiltinexec
 features: [regexp-named-groups]
 info: >
   Runtime Semantics: RegExpBuiltinExec ( R, S )
-    24. If R contains any GroupName,
-      a. Let groups be ObjectCreate(null).
-      b. Perform ! CreateDataProperty(A, "groups", groups).
+    24. If _R_ contains any |GroupName|, then
+      a. Let _groups_ be ObjectCreate(*null*).
+    25. Else,
+      a. Let _groups_ be *undefined*.
+    26. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
 ---*/
 
-// groups is created with Define, not Set
+// `groups` is created with Define, not Set.
 let counter = 0;
-Object.defineProperty(Array.prototype, "groups", {set() { counter++; }});
+Object.defineProperty(Array.prototype, "groups", {
+  set() { counter++; }
+});
+
 let match = /(?<x>.)/.exec("a");
 assert.sameValue(counter, 0);
 
-// groups is writable, enumerable and configurable
-// (from CreateDataProperty)
+// `groups` is writable, enumerable and configurable
+// (from CreateDataProperty).
 verifyProperty(match, "groups", {
   writable: true,
   enumerable: true,
   configurable: true,
 });
 
-// The '__proto__' property on the groups object is not special,
+// The `__proto__` property on the groups object is not special,
 // and does not affect the [[Prototype]] of the resulting groups object.
 let {groups} = /(?<__proto__>.)/.exec("a");
 assert.sameValue("a", groups.__proto__);