wpt / css / css-cascade / scope-specificity.html

Status: FAIL | Duration: 55ms | Subtests: 0/11 | Open test on wpt.live | wpt.fyi

Data from commit:
a50cb89 wpt: run reference-named files which are themselves tests (#836) (2026-09-03)

SubtestStatusMessage
@scope (#main) { .b { } } and .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) to (.b) { .a { } } and .aFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main, .foo, .bar) { #a { } } and #aFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { div.b { } } and div.bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { :scope .b { } } and .a .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { & .b { } } and :where(#main) .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { div .b { } } and div .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { @scope (.a) { .b { } } } and .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { :scope .b { } } and :scope .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope { & .b { } } and :where(:scope) .bFAILassert_equals: scoped rule expected "1" but got "auto"
@scope (#main) { > .a { } } and :where(#main) > .aFAILassert_equals: scoped rule expected "1" but got "auto"

/css/css-cascade/scope-specificity.html

<!DOCTYPE html>
<title>@scope - specificity</title>
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style id=style>
</style>
<main id=main>
  <style id=styleImplicit></style>
  <div id=a class=a>
    <div id=b class=b>
    </div>
  </div>
</main>
<script>

// Format a scoped style rule using the selector at scoped_selector's last element,
// with each preceding array item representing an enclosing @scope rule.
//
// Example:
//
//  scoped_selector=['@scope (foo)', '@scope (bar)', 'div']
//  declarations='z-index:42'
//    => '@scope (foo) { @scope (bar) { div { z-index:42 } } }'
function format_scoped_rule(scoped_selector, declarations) {
  if (scoped_selector.length < 2) {
    throw "Fail";
  }
  let scope_prelude = scoped_selector[0];
  let remainder = scoped_selector.slice(1);
  let content = remainder.length == 1
                  ? `${remainder[0]} { ${declarations} }`
                  : format_scoped_rule(remainder, declarations);
  return `${scope_prelude} { ${content} }`;
}

// Verify that the specificity of 'scoped_selector' is the same
// as the specificity of 'ref_selector'. Both selectors must select
// an element within #main.
function test_scope_specificity(scoped_selector, ref_selector, style) {
  if (style === undefined) {
    style = document.getElementById("style");
  }
  test(t => {
    t.add_cleanup(() => { style.textContent = ''; });

    let element = main.querySelector(ref_selector);
    assert_not_equals(element, null);

    let scoped_rule = format_scoped_rule(scoped_selector, 'z-index:1');
    let ref_rule = `:is(${ref_selector}) { z-index:2 }`;

    style.textContent = `${scoped_rule}`;
    assert_equals(getComputedStyle(element).zIndex, '1', 'scoped rule');

    style.textContent = `${ref_rule}`;
    assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped rule');

    // The scoped rule should win due to proximity.
    style.textContent = `${scoped_rule} ${ref_rule}`;
    assert_equals(getComputedStyle(element).zIndex, '1', 'scoped + unscoped');

    // The scoped rule should win due to proximity (reverse).
    style.textContent = `${ref_rule} ${scoped_rule}`;
    assert_equals(getComputedStyle(element).zIndex, '1', 'unscoped + scoped');

    // Add one (1) to the specificty of the unscoped rule. This should
    // cause the unscoped rule to win instead.
    style.textContent = `div${ref_rule} ${scoped_rule}`;
    assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped + scoped');
  }, format_scoped_rule(scoped_selector, '') + ' and ' + ref_selector);
}

// Selectors within @scope implicitly have `:scope <descendant-combinator>`
// added, but no specificity associated with it is added.
// See https://github.com/w3c/csswg-drafts/issues/10196
test_scope_specificity(['@scope (#main)', '.b'], '.b');
test_scope_specificity(['@scope (#main) to (.b)', '.a'], '.a');
test_scope_specificity(['@scope (#main, .foo, .bar)', '#a'], '#a');
test_scope_specificity(['@scope (#main)', 'div.b'], 'div.b');
test_scope_specificity(['@scope (#main)', ':scope .b'], '.a .b');
// & behaves like :where(:scope) - No specificity is added.
// See https://github.com/w3c/csswg-drafts/issues/9740
test_scope_specificity(['@scope (#main)', '& .b'], ':where(#main) .b');
test_scope_specificity(['@scope (#main)', 'div .b'], 'div .b');
test_scope_specificity(['@scope (#main)', '@scope (.a)', '.b'], '.b');
// Explicit `:scope` adds specficity.
test_scope_specificity(['@scope (#main)', ':scope .b'], ':scope .b');
// & behaves like :where(:scope), even for implicit scope roots.
test_scope_specificity(['@scope', '& .b'], ':where(:scope) .b', styleImplicit);
// Using relative selector syntax does not add specificity
test_scope_specificity(['@scope (#main)', '> .a'], ':where(#main) > .a');
</script>