From 587e3528a91176fd11b1a4acc2d5aec7683e199b Mon Sep 17 00:00:00 2001 From: Emil Ghitta Date: Wed, 22 Jul 2026 11:11:32 +0300 Subject: [PATCH] Improve user profile page --- .../jinja2/sumo/includes/entity_card.html | 31 ++ .../sumo/scss/components/_entity-card.scss | 78 ++++ .../static/sumo/scss/components/_index.scss | 3 +- .../static/sumo/scss/layout/_profile.scss | 378 +++++++++++++++ kitsune/users/jinja2/users/base.html | 14 +- kitsune/users/jinja2/users/profile.html | 432 ++++++++++-------- kitsune/users/tests/test_templates.py | 4 +- .../pages/user_pages/my_profile_page.py | 6 +- 8 files changed, 748 insertions(+), 198 deletions(-) create mode 100644 kitsune/sumo/jinja2/sumo/includes/entity_card.html create mode 100644 kitsune/sumo/static/sumo/scss/components/_entity-card.scss diff --git a/kitsune/sumo/jinja2/sumo/includes/entity_card.html b/kitsune/sumo/jinja2/sumo/includes/entity_card.html new file mode 100644 index 00000000000..638b22a43a8 --- /dev/null +++ b/kitsune/sumo/jinja2/sumo/includes/entity_card.html @@ -0,0 +1,31 @@ +{# Shared sidebar summary card for a person or group: avatar, name, an + optional username/meta line, an optional location line, an optional + primary action, and — when called with {% call %} — a .stat-list slot + wrapping whatever stats markup the caller supplies. + + This macro only owns the entity's own identity summary. Quick actions, + moderator tools, and hierarchy info are separate cards the caller places + alongside it, not more arguments here. + + Usage: + {% from "sumo/includes/entity_card.html" import entity_card %} + {% call entity_card(avatar_url, name, location=city) %} +

Whatever stats markup belongs in this entity's card.

+ {% endcall %} +#} +{% macro entity_card(avatar_url, name, username=None, location=None, primary_action=None) %} +{# location/primary_action are often captured via {% set x %}...{% endset %} + blocks whose inner {% if %} didn't match — that leaves whitespace, not an + empty string, so check the stripped content rather than truthiness. #} +
+ +

{{ name }}{% if username %} ({{ username }}){% endif %}

+ {% if location and location.strip() %}

{{ location.strip() }}

{% endif %} + {% if primary_action and primary_action.strip() %}
{{ primary_action }}
{% endif %} + {% if caller %} +
+ {{ caller() }} +
+ {% endif %} +
+{% endmacro %} \ No newline at end of file diff --git a/kitsune/sumo/static/sumo/scss/components/_entity-card.scss b/kitsune/sumo/static/sumo/scss/components/_entity-card.scss new file mode 100644 index 00000000000..7c6cc4ce1f3 --- /dev/null +++ b/kitsune/sumo/static/sumo/scss/components/_entity-card.scss @@ -0,0 +1,78 @@ +@use '../config' as c; +@use 'protocol/css/includes/lib' as p; + +// Entity card +// +// A sidebar summary card for a person or group: avatar, name, an optional +// meta line, an optional primary action, and an optional .stat-list of +// stat-item rows. +// +// Markup: ../../../../../../styleguide/styleguide-examples/entity-card.njk +// +// Weight: 6 +// +// Style guide: entity-card + +// Base card look (padding, radius, shadow) comes from the shared .card and +// .elevation-01 classes applied alongside this one in markup — only the +// bits specific to this component live here. +.entity-card { + margin-bottom: p.$spacing-lg; + background: white; + text-align: center; + + &--avatar { + display: block; + width: 96px; + height: 96px; + margin: 0 auto p.$spacing-sm; + border-radius: 50%; + border: 3px solid var(--color-marketing-gray-03); + object-fit: cover; + } + + &--name { + @include c.sumo-callout-heading('no-fam'); + margin: 0 0 2px; + // A long display name or username (often one unbroken word) has no + // natural wrap point, and as a flex item this card won't shrink below + // that text's width by default — it forces the sidebar column wider + // than its assigned width instead of wrapping. + overflow-wrap: anywhere; + } + + &--location { + @include c.text-body-sm; + color: var(--color-marketing-gray-06); + margin: 0; + font-weight: normal; + overflow-wrap: anywhere; + } + + &--action { + margin-top: p.$spacing-md; + + .sumo-button { + width: 100%; + } + } + + &--secondary-link { + display: block; + margin-top: p.$spacing-xs; + @include c.text-body-xs; + color: var(--color-marketing-gray-06); + text-decoration: none; + + &:hover { + color: var(--color-link); + } + } + + .stat-list { + margin-top: p.$spacing-md; + padding-top: p.$spacing-md; + border-top: 1px solid var(--color-marketing-gray-03); + text-align: left; + } +} \ No newline at end of file diff --git a/kitsune/sumo/static/sumo/scss/components/_index.scss b/kitsune/sumo/static/sumo/scss/components/_index.scss index 429fffde954..47e0f4d5d0c 100644 --- a/kitsune/sumo/static/sumo/scss/components/_index.scss +++ b/kitsune/sumo/static/sumo/scss/components/_index.scss @@ -22,4 +22,5 @@ @forward 'dashboards'; @forward 'flaggit'; @forward 'groups'; -@forward 'field-help-text'; \ No newline at end of file +@forward 'field-help-text'; +@forward 'entity-card'; diff --git a/kitsune/sumo/static/sumo/scss/layout/_profile.scss b/kitsune/sumo/static/sumo/scss/layout/_profile.scss index e22621bf7e3..b5f697b58c7 100644 --- a/kitsune/sumo/static/sumo/scss/layout/_profile.scss +++ b/kitsune/sumo/static/sumo/scss/layout/_profile.scss @@ -1,6 +1,384 @@ @use '../config' as c; @use 'protocol/css/includes/lib' as p; +// Wider sidebar for the identity card, same adjustment _groups.scss makes +// for #group-profile — scoped to .entity-card rather than #profile, since +// #profile is reused (unadorned) by the other "My Account" pages. +body:has(.entity-card) { + @media #{p.$mq-lg} { + .sumo-l-two-col--sidebar { + width: c.col-width(3.1, 8); + } + + .sumo-l-two-col--main { + width: c.col-width(4.9, 8); + } + } + + @media #{p.$mq-xl} { + .sumo-l-two-col--sidebar { + width: c.col-width(3.1, 10); + } + + .sumo-l-two-col--main { + width: c.col-width(6.9, 10); + } + } +} + +// "Report Abuse" (from the shared flag_form() macro, which only ever +// renders a bare ) sits in the entity card's +// action slot alongside the private-message button, so it needs a quieter +// treatment than the button next to it — matching the mockup's .link-quiet. +// Targeted by the modal-trigger attribute rather than a class, since +// flag_form() doesn't accept one and stays untouched. +.entity-card--action a[data-sumo-modal] { + display: block; + margin-top: p.$spacing-xs; + text-align: center; + @include c.text-body-xs; + color: var(--color-marketing-gray-06); + + &:hover { + color: var(--color-error); + } +} + +// Generic accent-rule heading, used to wrap a heading that's free to move +// outside its section (About/Badges). Groups keeps its own heading inside +// section.groups for a locator that requires it there — styled separately +// below via #profile .card section.groups > h2. +#profile { + // Padding, radius, and shadow come from the shared .card/.elevation-01/ + // .lg-pad classes applied alongside this one in markup — only the + // page-specific stacking gap and background live here. + .card { + margin-bottom: p.$spacing-lg; + background: white; + } + + // Shown instead of the About/Badges/Groups cards when all three (and + // contact info) are unset, so the main column isn't just blank space next + // to the identity card. Dashed border + no elevation distinguishes it from + // real content cards at a glance, reusing the same .card shell otherwise. + .card--empty { + box-shadow: none; + border: 1px dashed var(--color-marketing-gray-04); + text-align: center; + + &-icon { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + margin: 0 auto p.$spacing-md; + border-radius: 50%; + background: var(--color-marketing-gray-01); + color: var(--color-marketing-gray-06); + + svg { + width: 22px; + height: 22px; + } + } + + h2 { + margin: 0 0 p.$spacing-sm; + } + + p { + margin: 0 auto p.$spacing-lg; + max-width: 42ch; + color: var(--color-text-light); + } + } + + .card-heading { + margin: 0 0 p.$spacing-lg; + padding-bottom: p.$spacing-md; + border-bottom: 2px solid var(--color-marketing-gray-03); + + h2 { + margin: 0; + } + } + + .card > section.groups > h2 { + margin: 0 0 p.$spacing-lg; + padding-bottom: p.$spacing-md; + border-bottom: 2px solid var(--color-marketing-gray-03); + } + + .card-heading h2, + .card > section.groups > h2 { + @include c.sumo-callout-heading; + position: relative; + padding-left: p.$spacing-md; + // "About {user}" / "{user}'s Groups" interpolate the display name — + // same unwrappable-long-name overflow as .entity-card--name, but here + // it blows out the main column and the whole page horizontally rather + // than just the sidebar card. + overflow-wrap: anywhere; + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + background: var(--color-link); + border-radius: p.$border-radius-sm; + } + } + + // Group membership as pill chips instead of a bare bulleted list. The + // group_link() helper still renders a plain (or plain text for groups + // without a GroupProfile) — the chip look is applied to the existing
  • + // wrapper so section.groups > ul > li stays intact for the Playwright + // groups_list_items locator. + section.groups ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: p.$spacing-sm; + } + + section.groups li { + position: relative; + display: inline-flex; + align-items: center; + gap: p.$spacing-xs; + padding: p.$spacing-xs p.$spacing-md; + background: var(--color-marketing-gray-01); + border: 1px solid var(--color-marketing-gray-03); + border-radius: p.$border-radius-lg; + @include c.text-body-sm; + font-weight: 600; + + svg { + flex-shrink: 0; + width: p.$spacing-md; + height: p.$spacing-md; + color: var(--color-link); + } + + a { + color: inherit; + text-decoration: none; + + // group_link() only wraps the group name in — the icon and the + // pill's own padding sit outside it, so without this the chip looks + // like one clickable unit but only the text responds. Stretching the + // link over the whole
  • (rather than moving the icon inside the + // ) needs no template/helper change. + &::after { + content: ''; + position: absolute; + inset: 0; + } + } + + &:hover { + border-color: var(--color-link); + + a { + color: var(--color-link); + } + } + } + + // The FxA-migrated "Your Account" callout — same left-accent-gradient + // treatment #group-profile already gives .card--callout. + .card--callout { + padding: p.$spacing-xl; + background: linear-gradient(135deg, var(--color-marketing-gray-01) 0%, white 100%); + border-left: 4px solid var(--color-link); + + .sumo-page-heading { + margin-top: 0; + } + } + + // About card: bio prose, then a divider, then the contact-details list — + // only when both are present, via the adjacent-sibling combinator so no + // extra Jinja conditional is needed for the divider itself. + .bio + .avatar-group--in-card { + margin-top: p.$spacing-lg; + padding-top: p.$spacing-lg; + border-top: 1px solid var(--color-marketing-gray-03); + } + + .avatar-group--details-icon { + display: inline-flex; + flex-shrink: 0; + color: var(--color-marketing-gray-06); + margin-right: p.$spacing-xs; + + svg { + width: p.$spacing-md; + height: p.$spacing-md; + } + } + + .avatar-group--in-card .avatar-group--details-list { + display: grid; + grid-template-columns: 1fr; + gap: p.$spacing-sm p.$spacing-xl; + list-style: none; + margin: 0; + padding: 0; + + @media #{p.$mq-sm} { + grid-template-columns: 1fr 1fr; + } + } + + .avatar-group--details-item { + display: flex; + align-items: baseline; + flex-wrap: wrap; + } + + .profile-privacy-hint { + @include c.text-body-sm; + color: var(--color-text-light); + } +} + +// Sidebar cards +.sumo-l-two-col--sidebar .card { + @include c.card; + @include c.elevation-01; + padding: p.$spacing-lg; + margin-bottom: p.$spacing-lg; + background: white; +} + +// Wraps the shared .sidebar-nav ("My Account" links, or the other user's +// name when viewing someone else's profile) so it visually belongs with the +// other sidebar cards. Rendered by users/base.html's side_top block, so this +// applies to every "My Account" page (edit profile, settings, etc.), not +// just profile.html — .sidebar-nav itself is untouched since it's also +// reused outside this section (e.g. the KB editor tools sidebar). +.sidebar-account-nav-card .sidebar-nav { + margin-bottom: 0; +} + +// Contribution stat rows, folded into the entity card's .stat-list (see +// entity_card()'s {% call %} usage in profile.html) so avatar, name, and +// contribution counts read as one card instead of two stacked ones. +.entity-card section.contributions { + h2 { + @include c.text-body-xs; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-marketing-gray-06); + margin: 0 0 p.$spacing-sm; + } + + ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: p.$spacing-sm; + } + + li { + padding: p.$spacing-sm p.$spacing-md; + background: var(--color-marketing-gray-01); + border-radius: p.$border-radius-sm; + border-left: 3px solid var(--color-link); + @include c.text-body-sm; + + a { + color: var(--color-heading); + font-weight: 700; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + // "Involved with Mozilla since …" reads as a quiet footer line below the + // stat rows (matching the identity-card mockup) rather than another stat + // row. It has to stay first in the DOM — the Playwright page object's + // contributed_from_info locator finds it by list position — so it's + // pushed to the end visually with flex order instead of being moved. + li.contributions--since { + order: 1; + margin-top: p.$spacing-xs; + padding: p.$spacing-xs p.$spacing-md 0; + background: none; + border-left: none; + @include c.text-body-xs; + color: var(--color-marketing-gray-06); + } +} + +.sidebar-moderator-card { + &--title { + @include c.text-body-xs; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-marketing-gray-06); + margin: 0 0 p.$spacing-md; + } + + #admin-actions { + margin-bottom: p.$spacing-sm; + } + + &--danger-zone { + padding-top: p.$spacing-sm; + border-top: 1px dashed var(--color-error); + display: flex; + flex-direction: column; + gap: p.$spacing-xs; + + .deactivate { + margin: 0; + } + + // These stay plain elements — the Playwright + // page object locates them by value text (deactivate_this_user_button + // etc.) — but get a real outlined-danger-button treatment instead of + // an unstyled OS control with just its text tinted red. + input[type="submit"] { + appearance: none; + width: 100%; + padding: p.$spacing-sm p.$spacing-sm; + background: transparent; + border: 1.5px solid var(--color-error); + border-radius: p.$border-radius-sm; + color: var(--color-error); + font-weight: 700; + text-align: center; + @include c.text-body-xs; + cursor: pointer; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; + + &:hover { + background: var(--color-error); + border-color: var(--color-error); + color: white; + } + + &:active { + background: var(--color-error-hover); + border-color: var(--color-error-hover); + } + } + } +} + .awards-table { table-layout: fixed; width: 100%; diff --git a/kitsune/users/jinja2/users/base.html b/kitsune/users/jinja2/users/base.html index 790edccb17e..2934aa3494d 100644 --- a/kitsune/users/jinja2/users/base.html +++ b/kitsune/users/jinja2/users/base.html @@ -15,17 +15,9 @@ {% if not profile and user.is_authenticated %} {% set profile = get_profile(user) %} {% endif %} - {% if profile and not profile.is_system_account %} - {% if request.user == profile.user %} + {% if profile and not profile.is_system_account and request.user == profile.user %} + {% endif %} {% endblock %} diff --git a/kitsune/users/jinja2/users/profile.html b/kitsune/users/jinja2/users/profile.html index 0e6e7fde8ea..b36c5664785 100644 --- a/kitsune/users/jinja2/users/profile.html +++ b/kitsune/users/jinja2/users/profile.html @@ -1,165 +1,81 @@ {% extends "users/base.html" %} {% from "wiki/includes/flag_form.html" import flag_form with context %} {% from "kbadge/includes/macros.html" import awards_list with context %} +{% from "sumo/includes/entity_card.html" import entity_card %} {% set title = _('{user} | Profile')|f(user=display_name(profile.user)) %} {% set classes = 'profile' %} {% set canonical_url = canonicalize(viewname='users.profile', username=profile.user.username) %} {% set active = 'my-profile' %} {% set is_owner = profile and request.user == profile.user %} +{% set can_edit_profile = request.user.is_authenticated and request.user != profile.user and not profile.is_system_account and user.has_perm('users.change_profile') %} +{% set can_report = request.user.is_authenticated and request.user != profile.user and not profile.is_system_account %} +{% set can_deactivate = user.id != profile.user.id and user.has_perm('users.deactivate_users') and not profile.is_system_account and not profile.user.is_superuser %} +{# profile.public_email only means the owner opted in to showing it — it's still + hidden from logged-out visitors below, so it can't count toward "there's + contact info to show" unless the current viewer can actually see it. #} +{% set can_view_public_email = profile.public_email and request.user.is_authenticated %} +{% set has_contact_info = can_view_public_email or profile.website or profile.twitter or profile.community_mozilla_org or profile.people_mozilla_org or profile.matrix_handle %} {% block breadcrumbs %}{% endblock %} -{% block content %} -
    - {% if is_owner and profile.is_fxa_migrated %} -

    {{ _('Your Account') }}

    -

    {{ user.email }}

    -

    - {# L10n: Deprecated. Learn more refers to https://support.mozilla.org/kb/change-primary-email-address-firefox-accounts #} - {% set deprecated_visit_moz_accounts %} - {% trans a_open=''|safe, a_close=''|safe %} - To change your email or avatar, visit the Mozilla account page. {{ a_open }} Learn more. {{ a_close }} - {% endtrans %} - {% endset %} - {# L10n: Learn more refers to https://support.mozilla.org/kb/change-primary-email-address-firefox-accounts #} - {% trans a_open=''|safe, a_close=''|safe %} - To change your email or avatar, visit the Mozilla accounts page. {{ a_open }}Learn more.{{ a_close }} - {% endtrans %} -

    -

    - {# L10n: Mozilla Support refers to https://support.mozilla.org #} - {% trans a_open=''|safe, a_close=''|safe %} - After changing your avatar, you will have to sign in again to {{ a_open }}Mozilla Support{{ a_close }} for the changes to be applied. - {% endtrans %} -

    -
    -
    - -
    -
    -
    - {% endif %} - {% if request.user.is_authenticated and request.user != profile.user and not profile.is_system_account %} - {% if user.has_perm('users.change_profile') %} - - {% endif %} - {% if not profile.is_system_account %} - {{ flag_form(url('users.flag', object_id=profile.pk), profile.id, False) }} - {% endif %} - {% endif %} -

    {{ display_name(profile.user) }} - {% if profile.name %} - ({{ profile.user.username }}) - {% endif %} -

    -

    - {% if is_owner %} - {# L10n: Deprecated. Edit your username refers to url("users.edit_my_profile"), which is https://support.mozilla.org/users/edit #} - {% set deprecated_edit_username %} - {% trans a_open=''|safe, a_close=''|safe %} - Your privacy is important to us. Your username is always visible to the public - - you can always {{ a_open }} edit your username {{ a_close }} if you would like to do so. - {% endtrans %} - {% endset %} - {# L10n: Edit your username refers to url("users.edit_my_profile"), which is https://support.mozilla.org/users/edit #} - {% trans a_open=''|safe, a_close=''|safe %} - Your privacy is important to us. Your username is always visible to the public – - you can always {{ a_open }}edit your username{{ a_close }} if you would like to do so. - {% endtrans %} - {% endif %} -

    - -

    - {% if profile.city and profile.country %} - {{ _('{city}, {country}')|f(city=profile.city, - country=profile.get_country_display() ) }} - {% elif profile.city %} - {{ profile.city }} - {% elif profile.country %} - {{ profile.get_country_display() }} - {% endif %} -

    +{# .sumo-l-two-col defaults to flex-direction: row-reverse at desktop widths, + which puts the DOM-first
    + {{ super() }} {% endblock %} + +{% block content %} +
    + {% if is_owner %} +
    + {% if profile.is_fxa_migrated %} +

    {{ _('Your Account') }}

    +

    {{ user.email }}

    +

    + {# L10n: Deprecated. Learn more refers to https://support.mozilla.org/kb/change-primary-email-address-firefox-accounts #} + {% set deprecated_visit_moz_accounts %} + {% trans a_open=''|safe, a_close=''|safe %} + To change your email or avatar, visit the Mozilla account page. {{ a_open }} Learn more. {{ a_close }} + {% endtrans %} + {% endset %} + {# L10n: Learn more refers to https://support.mozilla.org/kb/change-primary-email-address-firefox-accounts #} + {% trans a_open=''|safe, a_close=''|safe %} + To change your email or avatar, visit the Mozilla accounts page. {{ a_open }}Learn more.{{ a_close }} + {% endtrans %} +

    +

    + {# L10n: Mozilla Support refers to https://support.mozilla.org #} + {% trans a_open=''|safe, a_close=''|safe %} + After changing your avatar, you will have to sign in again to {{ a_open }}Mozilla Support{{ a_close }} for the changes to be applied. + {% endtrans %} +

    + {% endif %} +

    + {# L10n: Deprecated. Edit your username refers to url("users.edit_my_profile"), which is https://support.mozilla.org/users/edit #} + {% set deprecated_edit_username %} + {% trans a_open=''|safe, a_close=''|safe %} + Your privacy is important to us. Your username is always visible to the public - + you can always {{ a_open }} edit your username {{ a_close }} if you would like to do so. + {% endtrans %} + {% endset %} + {# L10n: Edit your username refers to url("users.edit_my_profile"), which is https://support.mozilla.org/users/edit #} + {% trans a_open=''|safe, a_close=''|safe %} + Your privacy is important to us. Your username is always visible to the public – + you can always {{ a_open }}edit your username{{ a_close }} if you would like to do so. + {% endtrans %} +

    +
    + {% endif %} + + {% if can_deactivate and not profile.user.is_active %} +
    {{ _('This user has been deactivated.') }}
    + {% endif %} + + {% if profile.bio or has_contact_info %} +
    +
    +

    {{ _('About {user}')|f(user=display_name(profile.user)) }}

    +
    + + {% if profile.bio %} +
    + {{ profile.bio|wiki_to_safe_html(nofollow=True) }} +
    + {% endif %} + + {% if has_contact_info %} +
    +
    +
      + {% if can_view_public_email %} +
    • + + + {{ profile.user.email|public_email }} +
    • + {% endif %} + {% if profile.website %} +
    • + + + {{ profile.website }} +
    • + {% endif %} + {% if profile.twitter %} +
    • + + + {{ profile.twitter }} +
    • + {% endif %} + {% if profile.community_mozilla_org %} +
    • + + + {{ profile.community_mozilla_org }} +
    • + {% endif %} + {% if profile.people_mozilla_org %} +
    • + + + {{ profile.people_mozilla_org }} +
    • + {% endif %} + {% if profile.matrix_handle %} +
    • + + + {{ profile.matrix_handle }} +
    • + {% endif %} +
    +
    +
    + {% endif %} +
    + {% endif %} + + {% if awards %} +
    +
    +

    {{ _('Badges') }}

    +
    +
    + {{ awards_list(awards) }} +

    {{ _('See all available SUMO badges') }}

    +
    +
    + {% endif %} + + {% if groups %} +
    +
    +

    {{ _("{user}'s Groups")|f(user=display_name(profile.user)) }}

    +
      + {% for g in groups %} +
    • + + {{ group_link(g.group) }} +
    • + {% endfor %} +
    +
    +
    + {% endif %} + + {% if not (profile.bio or has_contact_info or awards or groups) %} +
    + +

    {{ _('Nothing here yet') }}

    + {% if is_owner %} +

    {{ _("You haven't added a bio, joined a group, or earned a badge yet. Once you do, it'll show up here.") }}

    + {{ _('Edit your profile') }} + {% else %} +

    {{ _('{user} hasn\'t added a bio, joined a group, or earned a badge yet.')|f(user=display_name(profile.user)) }}

    + {% endif %} +
    + {% endif %} +
    +{% endblock %} \ No newline at end of file diff --git a/kitsune/users/tests/test_templates.py b/kitsune/users/tests/test_templates.py index 7ae709456b3..f8c14e91897 100644 --- a/kitsune/users/tests/test_templates.py +++ b/kitsune/users/tests/test_templates.py @@ -116,9 +116,9 @@ def test_view_ProfileFactory(self): self.assertEqual(200, r.status_code) doc = pq(r.content) self.assertEqual(0, doc("#edit-profile-link").length) - self.assertEqual(self.u.username, doc("h2.user").text()) + self.assertEqual(self.u.username, doc("h2.entity-card--name").text()) # No name set => no optional fields. - self.assertEqual(0, doc(".contact").length) + self.assertEqual(0, doc(".avatar-group--details-list").length) # Check canonical url self.assertEqual( "{}/en-US/user/{}/".format(settings.CANONICAL_URL, self.u.username), diff --git a/playwright_tests/pages/user_pages/my_profile_page.py b/playwright_tests/pages/user_pages/my_profile_page.py index 210f50da0b5..8b12f942b16 100644 --- a/playwright_tests/pages/user_pages/my_profile_page.py +++ b/playwright_tests/pages/user_pages/my_profile_page.py @@ -13,7 +13,7 @@ def __init__(self, page: Page): """Locators available for admin & other users.""" self.edit_user_profile_option = page.locator("div#admin-actions").get_by_role( "link").filter(has_text="Edit user profile") - self.report_abuse_profile_option = page.locator("article#profile").get_by_role( + self.report_abuse_profile_option = page.locator("aside#aside").get_by_role( "link").filter(has_text="Report Abuse") self.this_user_was_deactivated_message = page.locator("//div[@id='deactivated-msg']") self.deactivate_this_user_button = page.locator("input[value='Deactivate this user']") @@ -54,9 +54,9 @@ def __init__(self, page: Page): self.email_address = page.locator("p strong") self.displayed_email_address = page.locator( "li[class='avatar-group--details-item'] span[class='email'] a") - self.sign_out_button = page.locator("article#profile").get_by_role("link").filter( + self.sign_out_button = page.locator("aside#aside").get_by_role("link").filter( has_text="Sign Out") - self.display_name_header = page.locator("//h2[@class='sumo-callout-heading user']") + self.display_name_header = page.locator("h2.entity-card--name") self.display_name_by_username = lambda username: page.get_by_role( "heading", name=username, exact=True) self.username_info = page.locator("span[class='username']")