<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zoe Clegg</title>
    <description>The latest articles on DEV Community by Zoe Clegg (@zoekclegg).</description>
    <link>https://dev.to/zoekclegg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3540102%2F9fd47455-42d1-4e09-9f90-846dfd98624d.jpg</url>
      <title>DEV Community: Zoe Clegg</title>
      <link>https://dev.to/zoekclegg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zoekclegg"/>
    <language>en</language>
    <item>
      <title>Cousin locators in Playwright</title>
      <dc:creator>Zoe Clegg</dc:creator>
      <pubDate>Sun, 02 Nov 2025 22:17:47 +0000</pubDate>
      <link>https://dev.to/zoekclegg/cousin-locators-in-playwright-mk9</link>
      <guid>https://dev.to/zoekclegg/cousin-locators-in-playwright-mk9</guid>
      <description>&lt;p&gt;When your page renders multiple instances of the same element or component, your locator strategy needs to reliably be able to target the specific element that our test cares about. &lt;/p&gt;

&lt;p&gt;Using indexes might not work if the order or count of the elements is not consistent. Using dynamic test IDs (such as &lt;code&gt;edit-${username}-button&lt;/code&gt;) can sometimes be complex to create and might be brittle depending on the formatting of the dynamic part of the locator.&lt;/p&gt;

&lt;p&gt;Using what I call ‘cousin’ locators to help fetch the element you want is a much more robust and reliable solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using cousin locators
&lt;/h2&gt;

&lt;p&gt;In the example below, I want to click the ‘Edit’ button for the user Dawn Summers. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9myp7ahmo061xa1y1m9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9myp7ahmo061xa1y1m9s.png" alt="multiple profile containers each with a username, user id and edit button" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we just try clicking on the edit button like below, the test will fail as there are multiple elements that match that locator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await page.getByTestId('edit-profile-button').click()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we need to narrow down our search so it only looks at the button in Dawn’s profile container.&lt;/p&gt;

&lt;p&gt;We can start by choosing a unique element inside each container. User ID is a good example in this scenario. Assuming Dawn’s user ID remains constant between test runs, we can get this element using the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page.getByTestId(‘user-id’).filter({hasText: ‘920437’})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this, we can work upwards to get the user container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getProfileContainer(userId: string) {
  const userIdElement = page.getByTestId(‘user-id’).filter({hasText: userId})
  return page.getByTestId(‘profile-container’).filter({has: userIdElement})
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the correct profile container, we can look within this container and click on the edit button we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async clickEditProfileButton(userId: string) {
  const profileContainer = getProfileContainer(userId);
  await profileContainer.getByTestId(‘edit-profile-button’).click()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it. We can now reliably click the corresponding button based on the user ID we’ve supplied. Here’s a full look at this code in a page object model file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class profileList {
  readonly page: Page;
  readonly profileContainer: Locator;
  readonly userId: Locator;
  readonly editProfileButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.profileContainer = page.getByTestId(‘profile-container’);
    this.userId = page.getByTestId(‘user-id’);
    this.editProfileButton = page.getByTestId(‘edit-profile-button’);
  }

  getProfileContainer(userId: string) {
    const userIdElement = this.userId.filter({hasText: userId})
    return this.profileContainer.filter({has: userIdElement})
  }

  async clickEditProfileButton(userId: string) {
    const profileContainer = this.getProfileContainer(userId);
    await profileContainer.locator(this.editProfileButton).click()
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>automation</category>
    </item>
    <item>
      <title>Understanding the Birthday Paradox in Software Development</title>
      <dc:creator>Zoe Clegg</dc:creator>
      <pubDate>Sat, 18 Oct 2025 16:06:48 +0000</pubDate>
      <link>https://dev.to/zoekclegg/understanding-the-birthday-paradox-in-software-development-2oc8</link>
      <guid>https://dev.to/zoekclegg/understanding-the-birthday-paradox-in-software-development-2oc8</guid>
      <description>&lt;p&gt;If your code generates IDs or keys using some kind of random generator then it’s important that it generates unique values and not ones already in use. Universally Unique Identifier (UUID) is a widely used identifier which is usually a 36 character string, but depending on your use case you might not need something this long - particularly if the ID needs to be user-facing. But how do you know how many characters is enough? This is when the Birthday Paradox comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Birthday Paradox?
&lt;/h2&gt;

&lt;p&gt;In a room of just 23 people, there is a 50% chance that at least two of them share a birthday. Given that there’s 365 possible dates for each person’s birthday, it doesn’t sound right that you’d only need 23 people for that statement to be true. That’s why it’s called the Birthday Paradox. &lt;/p&gt;

&lt;h2&gt;
  
  
  The maths behind it
&lt;/h2&gt;

&lt;p&gt;Let’s say that &lt;strong&gt;p(n)&lt;/strong&gt; is the probability that in a room with &lt;strong&gt;n people&lt;/strong&gt;, nobody shares a birthday. This means &lt;strong&gt;1-p(n)&lt;/strong&gt; is the probability that at least two people share a birthday.&lt;/p&gt;

&lt;p&gt;If there’s only &lt;strong&gt;one person&lt;/strong&gt; in the room, they could have a birthday on any date, since there’s no one to share it with. So they have a &lt;strong&gt;365/365&lt;/strong&gt; chance of not sharing a birthday. &lt;/p&gt;

&lt;p&gt;When a &lt;strong&gt;second person&lt;/strong&gt; enters the room, in order for them not to share a birthday with the first person, they have a &lt;strong&gt;364/365&lt;/strong&gt; chance. A &lt;strong&gt;third person&lt;/strong&gt; would have &lt;strong&gt;363/365&lt;/strong&gt;, and so on…&lt;/p&gt;

&lt;p&gt;To determine the probability of all of the above statements to be true, we multiply the probabilities together. So the probability that &lt;strong&gt;none&lt;/strong&gt; of them share a birthday is:&lt;br&gt;


&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;p(n)=(365/365)∗(364/365)∗(363/365)∗…∗(365−(n−1)/365)
 p(n) = (365/365) * (364/365) * (363/365) * … * (365-(n-1)/365)
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;365/365&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;364/365&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;363/365&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="minner"&gt;…&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;365&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mord"&gt;/365&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;So when &lt;strong&gt;n = 23&lt;/strong&gt;, this means &lt;strong&gt;p(23) = 0.4927&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Therefore, &lt;strong&gt;1 -  p(23)&lt;/strong&gt;, the chance of at least two people sharing a birthday is &lt;strong&gt;0.5073&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  What does this mean for development?
&lt;/h2&gt;

&lt;p&gt;If we replace birthdays with ID values, the chance of two randomly generated IDs being the same is higher than you might intuitively have expected. In software, a clash could lead to errors, security risks or even data being overridden. Therefore it’s important to ensure that the number of possible unique IDs is large enough that the chance of the same ID being used twice is extremely low.&lt;/p&gt;

&lt;p&gt;The number of possible ID values you need is directly connected to the number of IDs you might expect to generate over the lifetime of your software. &lt;/p&gt;

&lt;p&gt;Let’s say your IDs refer to users of the software. Are you likely to be dealing with hundreds, thousands, millions or even more users?&lt;/p&gt;

&lt;p&gt;You can use this &lt;a href="https://www.statscalculators.com/resources/simulations/birthday-paradox-simulation#google_vignette" rel="noopener noreferrer"&gt;Birthday Paradox Calculator&lt;/a&gt; to work out how many possible ID values you should have to minimise clashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ways to combat the birthday paradox
&lt;/h2&gt;

&lt;p&gt;There are three common approaches to mitigating the birthday paradox: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increase the length of the ID:&lt;/strong&gt; more characters means more possible ID values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow more character types:&lt;/strong&gt; using a combination of letters, numbers and symbols leads to more possibilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement a retry:&lt;/strong&gt; as a fallback, if a generated ID already existed, generate another ID until it is unique&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>software</category>
      <category>development</category>
      <category>testing</category>
    </item>
    <item>
      <title>Six practical tips to improve your bug reports</title>
      <dc:creator>Zoe Clegg</dc:creator>
      <pubDate>Thu, 02 Oct 2025 10:06:17 +0000</pubDate>
      <link>https://dev.to/zoekclegg/six-practical-tips-to-improve-your-bug-reports-19bf</link>
      <guid>https://dev.to/zoekclegg/six-practical-tips-to-improve-your-bug-reports-19bf</guid>
      <description>&lt;p&gt;A poorly written bug report wastes time if it doesn’t include the information needed to prioritise and fix it. Here are some practical tips to help you get it right the first time and avoid unneeded back-and-forth with developers and product managers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Write a report everyone can understand
&lt;/h3&gt;

&lt;p&gt;Bug reports aren’t just for testers and developers - other stakeholders such as product managers and designers might also read them for prioritisation or other purposes. That doesn’t mean you should exclude technical details (such as error logs or environment details), but it does mean you should include summaries and reproduction steps that everyone can follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Be clear
&lt;/h3&gt;

&lt;p&gt;‘User can’t submit form’ doesn’t really convey what the problem is. Does nothing happen when the user clicks the submit button? Is the button disabled when it shouldn’t be? Is the button not visible? There are lots of ways to interpret this, so be sure to include the key details of the issue with clear reproduction steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Include full-page screenshots or videos
&lt;/h3&gt;

&lt;p&gt;If the problem involves a UI, ensure you include screenshots or videos that capture the whole of the page - including the URL of the page. Screenshots that are just small snippets of a page don’t always cut it. It should be immediately clear what page the user is on and where on the page the bug is (don’t be afraid to annotate your screenshots for extra clarity). This can help others locate the issue quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Create one report per bug
&lt;/h3&gt;

&lt;p&gt;Unless you’re fairly confident that two issues are due to the same bug in the code, it’s always best to create separate bug reports for each issue. This allows each issue to be managed individually and tracked more effectively. This also prevents bugs from getting lost or forgotten.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Highlight the impact of the bug
&lt;/h3&gt;

&lt;p&gt;Bugs that affect critical flows, cause poor user experiences or are potential security risks should be fixed quickly, so it’s important to make the bug’s impact clear in your report. This gives others the context they need to determine its priority. If the problem is only cosmetic or doesn’t affect user behaviour, mention that too.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Learn from the questions your reports get
&lt;/h3&gt;

&lt;p&gt;Every time someone asks you a question about one of your bug reports, think about how the answer could have been incorporated into the bug report from the start. This will show you what other information you should start including. But be careful, the more bloated your bug report is, the more likely it is that others will skim read it and end up asking you questions that your bug report already answers.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
    </item>
  </channel>
</rss>
