Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions images/chromium-headful/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
xdg-utils \
libvulkan1 \
fontconfig \
locales \
tzdata \
unzip && \
locale-gen en_US.UTF-8 en_GB.UTF-8 en_CA.UTF-8 en_IE.UTF-8 en_SG.UTF-8 && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# Install fonts to match a realistic Ubuntu 22.04 desktop fingerprint.
Expand Down Expand Up @@ -299,7 +297,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
libxext6 libxfixes3 libxkbcommon0;

# Install Chrome + ChromeDriver as a matched pair from chrome-for-testing so they cannot drift apart.
# Regional pack filenames preserve each locale identifier while reusing en-GB UI strings.
ARG CHROME_VERSION=152.0.7977.42
RUN set -eux; \
curl -fsSL "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" -o /tmp/chrome.zip; \
Expand All @@ -308,7 +305,6 @@ RUN set -eux; \
unzip -q /tmp/cd.zip -d /tmp; \
mv /opt/chrome-linux64 /opt/chrome-for-testing; \
mv /opt/chrome-for-testing/chrome /opt/chrome-for-testing/chromium; \
for locale in en-CA en-IE en-SG; do cp /opt/chrome-for-testing/locales/en-GB.pak "/opt/chrome-for-testing/locales/${locale}.pak"; done; \
ln -sf /opt/chrome-for-testing/chromium /usr/bin/chromium; \
mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver; \
chmod +x /usr/local/bin/chromedriver /opt/chrome-for-testing/chromium; \
Expand Down
4 changes: 0 additions & 4 deletions images/chromium-headless/image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,12 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
xclip \
xdotool \
fontconfig \
locales \
tzdata \
fonts-noto-cjk \
fonts-noto-color-emoji \
fonts-noto-mono \
fonts-nanum \
supervisor; \
locale-gen en_US.UTF-8 en_GB.UTF-8 en_CA.UTF-8 en_IE.UTF-8 en_SG.UTF-8; \
fc-cache -f

# sqlite3 for debugging the cookies file; unzip for the chrome-for-testing archives below.
Expand All @@ -174,7 +172,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
apt-get --no-install-recommends -y install sqlite3 unzip;

# Install Chrome + ChromeDriver as a matched pair from chrome-for-testing so they cannot drift apart.
# Regional pack filenames preserve each locale identifier while reusing en-GB UI strings.
ARG CHROME_VERSION=152.0.7977.42
RUN set -eux; \
curl -fsSL "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" -o /tmp/chrome.zip; \
Expand All @@ -183,7 +180,6 @@ RUN set -eux; \
unzip -q /tmp/cd.zip -d /tmp; \
mv /opt/chrome-linux64 /opt/chrome-for-testing; \
mv /opt/chrome-for-testing/chrome /opt/chrome-for-testing/chromium; \
for locale in en-CA en-IE en-SG; do cp /opt/chrome-for-testing/locales/en-GB.pak "/opt/chrome-for-testing/locales/${locale}.pak"; done; \
ln -sf /opt/chrome-for-testing/chromium /usr/bin/chromium; \
mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver; \
chmod +x /usr/local/bin/chromedriver /opt/chrome-for-testing/chromium; \
Expand Down
112 changes: 92 additions & 20 deletions server/e2e/e2e_browser_location_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"os/exec"
"strings"
"testing"
"time"

Expand All @@ -17,45 +18,116 @@ type browserLocationProbe struct {
TimeZone string `json:"timeZone"`
}

func TestRegionalBrowserLocation(t *testing.T) {
// Image names are pointers because init sets them after package variables.
var browserVariants = []struct {
name string
image *string
}{
{name: "headful", image: &headfulImage},
{name: "headless", image: &headlessImage},
}

// TestRegionalBrowserTimezone starts a browser in Singapore time. The images
// ship tzdata but no generated regional POSIX locales, so guest processes use
// C.UTF-8 for UTF-8 text.
func TestRegionalBrowserTimezone(t *testing.T) {
if _, err := exec.LookPath("docker"); err != nil {
t.Skipf("docker not available: %v", err)
}

for _, test := range []struct {
name string
image string
}{
{name: "headful", image: headfulImage},
{name: "headless", image: headlessImage},
} {
t.Run(test.name, func(t *testing.T) {
for _, variant := range browserVariants {
t.Run(variant.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()

c := NewTestContainer(t, test.image)
c := NewTestContainer(t, *variant.image)
require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{
"TZ": "Asia/Singapore",
"LANG": "en_SG.UTF-8",
"LC_ALL": "en_SG.UTF-8",
"CHROMIUM_FLAGS": "--lang=en-SG --accept-lang=en-SG,en --remote-allow-origins=*",
"TZ": "Asia/Singapore",
"KERNEL_BROWSER_TIMEZONE": "Asia/Singapore",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"CHROMIUM_FLAGS": "--remote-allow-origins=*",
}}), "failed to start container")
defer c.Stop(ctx)

require.NoError(t, c.WaitReady(ctx), "api not ready")
require.NoError(t, c.WaitDevTools(ctx), "devtools not ready")

osLocation, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `printf '%s|%s' "$(locale charmap)" "$(date +%z)"`})
// Read the system timezone from /etc/localtime, not the TZ variable.
osLocation, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `printf '%s|%s' "$(locale charmap)" "$(env -u TZ date +%z)"`})
require.NoError(t, err, "failed to inspect OS locale and timezone")
require.Equal(t, "UTF-8|+0800", osLocation)

browserLocation, err := evaluateBrowserLocation(ctx, c.CDPURL())
require.NoError(t, err)
require.Equal(t, "Asia/Singapore", browserLocation.TimeZone)
})
}
}

// TestRegionalBrowserLocale checks that the browser, not guest POSIX locale
// data or copied resource packs, provides the regional locale: at startup from
// production's --lang flags while the regional LANG/LC_ALL locale is not
// installed, and at runtime from a native location update applied under
// C.UTF-8.
func TestRegionalBrowserLocale(t *testing.T) {
requireKernelBrowser(t)
singapore := browserLocationProbe{Language: "en-SG", Locale: "en-SG", TimeZone: "Asia/Singapore"}

for _, variant := range browserVariants {
t.Run(variant.name+"/startup", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()

c := NewTestContainer(t, *variant.image)
require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{
"TZ": "Asia/Singapore",
"KERNEL_BROWSER_TIMEZONE": "Asia/Singapore",
"LANG": "en_SG.UTF-8",
"LC_ALL": "en_SG.UTF-8",
"CHROMIUM_FLAGS": "--lang=en-SG --accept-lang=en-SG,en --remote-allow-origins=*",
}}), "failed to start container")
defer c.Stop(ctx)
require.NoError(t, c.WaitReady(ctx), "api not ready")
require.NoError(t, c.WaitDevTools(ctx), "devtools not ready")

installed, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `locale -a | grep -ci '^en_sg' || true`})
require.NoError(t, err)
require.Equal(t, "0", strings.TrimSpace(installed), "the regional POSIX locale must be absent for this check to prove the browser does not use it")

browserLocation, err := evaluateBrowserLocation(ctx, c.CDPURL())
require.NoError(t, err)
require.Equal(t, singapore, browserLocation)
})

t.Run(variant.name+"/native", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()

c := NewTestContainer(t, *variant.image)
require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{
"TZ": "America/Los_Angeles",
"KERNEL_BROWSER_TIMEZONE": "America/Los_Angeles",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"CHROMIUM_FLAGS": "--lang=en-US --accept-lang=en-US,en --remote-allow-origins=*",
"KERNEL_INSTANCE_JWT": locationInstanceToken,
}}), "failed to start container")
defer c.Stop(ctx)
require.NoError(t, c.WaitReady(ctx), "api not ready")
require.NoError(t, c.WaitDevTools(ctx), "devtools not ready")

bundle := imageLocationBundle{Epoch: "lease-a", Generation: 1, TimeZone: "Asia/Singapore", Locale: "en-SG", Languages: []string{"en-SG", "en"}}
resetLocation(ctx, t, c, "", bundle)
waitLocationApplied(ctx, t, c, bundle)

osLocation, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `printf '%s|%s' "$(locale charmap)" "$(env -u TZ date +%z)"`})
require.NoError(t, err, "failed to inspect OS locale and timezone")
require.Equal(t, "UTF-8|+0800", osLocation)

browserLocation, err := evaluateBrowserLocation(ctx, c.CDPURL())
require.NoError(t, err)
require.Equal(t, browserLocationProbe{
Language: "en-SG",
Locale: "en-SG",
TimeZone: "Asia/Singapore",
}, browserLocation)
require.Equal(t, singapore, browserLocation)
})
}
}
Expand Down