Category: Development

Inside Nebula: My First Six Weeks as a Consultant

Nebula Consulting is a company I have admired from the very beginning – their values deeply resonated with me and aligned with my own work ethics. Reading positive posts about Nebula Consulting on social media further reinforced my admiration and respect for the company. Having followed them closely for some time, I was drawn to their expertise, culture and the meaningful impact they create. So for me, joining Nebula Consulting as a Functional Consultant truly felt like a dream come true.

Expertise at its Best

My first six weeks at Nebula have been nothing short of exciting and enriching. Each day has been filled with opportunities to learn, train, shadow and support colleagues. This experience has deepened my understanding of customer interactions and has given me valuable insights into Nebula’s culture, team dynamics and my role within the company.

In my first month, I had the opportunity to meet every member of the Nebula team, including our CEO, Sarah Kelleher and Director of Consulting,  Jack Bailey-Grundy. These introductions provided a fantastic way to immerse myself in Nebula’s culture, vision and values from day one. They also played a crucial role in helping me integrate into the team and understand how each individual contributes to our collective success. 

A significant part of my learning involved shadowing Salesforce Consultants during weekly customer meetings and training sessions. Observing different approaches firsthand allowed me to ask questions, share insights and gain a deeper understanding of best practices. It was inspiring to see the level of preparation, proactive thinking and seamless delivery that our Consultants bring to every customer interaction, demonstrating expertise and efficiency in action.

Another key aspect of my learning was Nebula’s delivery framework – a structured approach that leverages documents, processes and methodologies to manage and deliver projects efficiently. Through this, I gained a deeper appreciation for how structured project planning directly impacts successful delivery.

To put these methods into practice, I applied them while managing my task of writing my first blog using the Teamwork tool. Leveraging Teamwork’s features helped me organise tasks effectively, track progress and collaborate more efficiently, ultimately improving visibility and accountability. This hands-on experience reinforced the importance of structured planning in ensuring smooth project execution.

Engaging in Learning, Training and Development

Nebula Consulting is truly committed to continuous learning, ensuring that the team is always growing, evolving and staying ahead. Over the past few weeks, I’ve had the opportunity to participate in various team engagements and training sessions, each contributing to my professional development and deepening my understanding of our work.

  • Knowledge Share – This is an excellent platform for us to share knowledge, exchange customer insights, best practices and industry trends. Here, I learnt about the new Marketing Cloud Growth and Advanced editions, Elements Cloud as well as Spring ’25 Salesforce release highlights across various clouds. Additionally, our Customer Spotlight sessions provided valuable insights into our customers, their industries and how Nebula supports their success.
  • Personal Development – Through our Learning and Development sessions, I’ve been able to identify my strengths and enhance my self-awareness. This has allowed me to adapt my approach, work more effectively, collaborate better with our teams and drive impactful outcomes for our stakeholders.
  • Training – Time-Management training sessions have helped me better plan and organise my work, ensuring I prioritise tasks effectively and efficiently as well as align them with SMART objectives. I also completed essential compliance training including Health and Safety, and GDPR among others.
  • NebConnect sessions – A fantastic initiative designed to strengthen team connections and enhance collaboration to create a supportive work environment. Participating in these sessions has helped me build relationships, gain new perspectives and feel more connected to the Nebula community.
  • Salesforce Certifications –  In addition to all the learning and training, I have also been dedicating time to prepare for my Sales Cloud Certification to further strengthen my expertise and deliver greater value to our customers.

Agentforce Hackathon

One of the most exciting milestones of my first month was participating in the Agentforce Hackathon – an incredible opportunity to collaborate with some of the brightest minds at Nebula. Together, we worked on building our first AI Agent, brainstorming practical use cases that could benefit both customers and employees. This challenge pushed our creativity and problem-solving skills, and we successfully developed our Agent.

Building on this success, I also had the opportunity to write my first blog on The Power of Prompt Builder in Agentforce. Contributing to our Agentforce series allowed me to reflect on my learnings and share valuable insights with a broader audience.

The Nebula Culture

Beyond the learning and training, what truly sets Nebula apart is its culture and people. The values of trust, collaboration, empowerment and flexibility are deeply embedded in the work environment. I am fortunate to have a supportive team that provides structure and encouragement, empowering me to grow both professionally and personally. The openness to knowledge-sharing and collaboration, support customer success while focusing on best practices and continuous learning have been instrumental in my successful start to this new chapter. All this has helped me quickly develop the skills needed to thrive in this role.

Looking Ahead

The first six weeks have been a whirlwind of learning and I am excited about what’s to come. As I take on more responsibilities, actively supporting projects and closely collaborating with stakeholders, I look forward to deepening my expertise, contributing to Nebula’s success and growing alongside an amazing team!

If you’re looking for a dynamic, supportive environment to grow in Salesforce Consulting, Nebula Consulting is a great place to be!

UI Test Automation Model (UTAM)

What is E2E testing?

End-to-end testing is a software testing methodology usually adopted at the final phase of the software development life cycle. Its purpose is to check entire journeys are working as expected from a user’s perspective.

By simulating user interactions with the UI, you can create consistent automated tests which focus on specific journeys in your system. An example journey could be a checkout process for purchasing items. This would simulate user interactions, from adding an item to the basket and checking out to inputting card details. These interactions can all be automated via code and periodically run against a target environment.

The benefits of these tests can help identify issues in the system where unit and integration testing are not able to help. It’s important to understand that end-to-end testing does not replace unit or integration testing, however, they are a valuable addition.

What is UTAM?

UTAM is a UI testing framework which assists with end-to-end testing. It’s based on POM (Page Object Model) which helps to decouple the test from the target webpage. UTAM ships with its own page object grammar authored in JSON, this ultimately makes it easier to read, write and maintain.

UTAM Flow Diagram

With 3 major releases each year, Salesforce is prone to changes with some critical components being updated silently. This poses a problem for E2E test automation because the tests may require updating to reflect the changes that Salesforce made even if your code under test has not changed. Since UTAM is developed by Salesforce and they are closer to the teams that provide these updates, they can maintain page objects more quickly. Therefore, if E2E testing is something you’re planning to do, UTAM would be a good option.

What about Jest?

If you’ve created LWC components, you’ll be familiar with Jest tests. It’s the default testing framework when creating a new SFDX project and it is the go-to framework that Salesforce advertises when unit testing LWC components.

Unit testing and E2E testing are different methodologies. Both are used to test your application but from different perspectives.

When Salesforce talks about ‘Jest tests’, they talk about unit testing. The code is tested directly and we assert certain functions, calculations and logic are working as expected. This differs from E2E testing, where this methodology focuses on the rendered output of the components. “Does clicking this UI button result in a toast message to display?”, “Does following this UI journey show the correct components?” – these are some example scenarios E2E testing will help ascertain.

 

Aidan Harding
Technology Director

Looking for help with Salesforce development?

Get in Touch

Getting Started A Simple Example

Use the below HTML snippet as an example:

<html>
<head>
    <title>Hello World!</title>
</head>
<body>
    <h1>
        Hello,
        <span class="world">🌍 🌏 🌎</span>
</h1>
</body>
</html>

helloWorld.html

UTAM HTML Example

The JSON page object representation looks like this:

{
    "root": true,
    "selector": {
        "css": "body"
    },
    "elements": [
        {
            "name": "world",
            "selector": {
                "css": ".world"
            },
            "public": true
        }
    ]
}

helloWorld.utam.json

If you’ve installed and set up UTAM correctly (installation guide), you should be able to run this CLI command:
utam -c utam.config.js

This will build, compile and spit out your javascript page object and the result will look something like this:

import { By as _By, createUtamMixinCtor as _createUtamMixinCtor, UtamBaseRootPageObject as _UtamBaseRootPageObject } from '@utam/core';

async function _utam_get_world(driver, root) {
    let _element = root;
    const _locator = _By.css(".world");
    return _element.findElement(_locator);
}

export default class UnknownPageObjectName extends _UtamBaseRootPageObject {
    constructor(driver, element, locator = _By.css("body")) {
        super(driver, element, locator);
    }

    async __getRoot() {
        const driver = this.driver;
        const root = await this.getRootElement();
        const BaseUtamElement = _createUtamMixinCtor();
        return new BaseUtamElement(driver, root);
    }
    
    async getWorld() {
        const driver = this.driver;
        const root = await this.getRootElement();
        const BaseUtamElement = _createUtamMixinCtor();
        let element = await _utam_get_world(driver, root);
        element = new BaseUtamElement(driver, element);
        return element;
    }
}

helloWorld.js

You can then use this page object in your test and write the below test to assert the value within the span tags:

// Import a root page object
import HelloWorld from 'project-directory/helloWorld';

assertEmojis(async () =&gt; {
    // Load the page object
    const helloWorld = await utam.load(HelloWorld);

    // Call a UTAM-generated method to get an element.
    const worldIcon = await helloWorldRoot.getWorld();

    const emoji = await worldIcon.getText();
    assert.strictEqual(emoji, '🌍 🌏 🌎');
});

helloWorld.spec.js

To run the test, in the terminal type in the below. This initialises the Webdriver IO package and runs tests with a defined name.
wdio

The power of POM and UTAM gives you flexibility if the DOM tree changes. For example, if an extra HTML element is introduced making the <span> tag 1 level deeper, you would only need to update the JSON UTAM file to reflect this extra child element; leaving the actual test the same.

An LWC Example

We’ve seen how to test standard HTML files, let’s take a look at an LWC component.

Below is a basic form that uses lightning-record-edit-form and displays 3 standard fields from the Account object.

<template>
    <lightning-record-edit-form record-id={recordId} object-api-name="Account" onsubmit={handleSubmit} onsuccess={handleSuccess}>
        <div class="slds-text-heading_small slds-p-bottom_small"><strong>Simple LWC Form</strong></div>
        <lightning-input-field field-name="Name" value={name} required="true"></lightning-input-field>
        <lightning-input-field field-name="Phone" value={phone}></lightning-input-field>
        <lightning-input-field field-name="Description" value={description}></lightning-input-field>
        <div class="slds-card__footer slds-text-align_right">
            <lightning-button
                    type="submit"
                    variant="brand"
                    label="Update"
            ></lightning-button>
        </div>
    </lightning-record-edit-form>
</template>

simpleForm.html

UTAM LWC Example

The JSON page object representation looks like this:

{
    "shadow": {
        "elements": [
            {
                "public": true,
                "name": "recordEditForm",
                "type": "utam-sfdx/pageObjects/recordEditForm",
                "selector": {
                    "css": "lightning-record-edit-form"
                }
            }
        ]
    },
    "description": {
        "author": "nebula",
        "text": ["Page Object: simpleForm"]
    }
}

simpleForm.utam.json
{
    "shadow": {
        "elements": [
            {
                "public": true,
                "name": "recordEditFormEdit",
                "selector": {
                    "css": "lightning-record-edit-form-edit"
                },
                "elements": [
                    {
                        "public": true,
                        "name": "accountName",
                        "type": "salesforce-pageobjects/lightning/pageObjects/inputField",
                        "selector": {
                            "css": "lightning-input-field:nth-of-type(1)"
                        }
                    },
                    {
                        "public": true,
                        "name": "phone",
                        "type": "salesforce-pageobjects/lightning/pageObjects/inputField",
                        "selector": {
                            "css": "lightning-input-field:nth-of-type(2)"
                        }
                    },
                    {
                        "public": true,
                        "name": "description",
                        "type": "salesforce-pageobjects/lightning/pageObjects/inputField",
                        "selector": {
                            "css": "lightning-input-field:nth-of-type(3)"
                        }
                    },
                    {
                        "public": true,
                        "name": "footer",
                        "selector": {
                            "css": ".slds-card__footer"
                        },
                        "elements" : [
                            {
                                "public": true,
                                "name": "updateButton",
                                "type": "salesforce-pageobjects/lightning/pageObjects/button",
                                "selector": {
                                    "css": "lightning-button"
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    "description": {
        "author": "nebula",
        "text": ["Page Object: recordEditForm"]
    }
}

recordEditForm.utam.json

We’ve added a level of abstraction, where simpleForm.utam.json looks for recordEditForm.utam.json which contain the input fields. This utilises the salesforce-pageobjects library (more on this in the next section).

Once the above page objects are compiled, simpleForm and recordEditForm javascript page objects will be available for you to call in your tests.

// Find the simpleForm component
const component = await activeTab.getComponent2('c_simpleForm');
const simpleForm = await component.getContent(SimpleForm);
const recordEditForm = await simpleForm.getRecordEditForm(RecordEditForm);
const accountName = await recordEditForm.getAccountName();
const accountNameInput = await accountName.getInput();

// Check there is already a value in the input field
expect(await accountNameInput.getValueText()).not.toBeNull();

// Find and set the Account Name and then click on the update button
await accountNameInput.setText(randomAccountName);
await(await recordEditForm.getUpdateButton()).click();

// Check the Account Name value has been updated using the highlights panel
const recordDesktop = await utam.load(RecordHomeTemplateDesktop);
const formattedText = await (await (await (await recordDesktop.getHighlights()).getRecordLayout()).waitForHighlights2()).getPrimaryFieldContent(FormattedText);

expect(await formattedText.getInnerText()).toContain(randomAccountName);

simpleForm.spec.js

But my components aren’t that simple!

I hear you. To focus on the basics and keep the code snippets small, the examples in this blog are simple on purpose.

UTAM JS Recipes is a great place to start if you wanted to see complex examples. The repository contains tests against standard/custom components and even an example of an E2E test that’s not part of the Salesforce platform. This repository utilises a Node package called “salesforce-pageobjects” – this package is created and maintained by Salesforce and consists of all standard components you may find when interacting with the Salesforce Lightning UI. It’s best to understand which page objects are available to you before writing your own JSON UTAM representation of the DOM tree.

Let’s end-to-end test everything!

Although there are benefits to end-to-end testing outlined in this blog, it may not be wise to create E2E scripts for everything. A few reasons for this include:

  1. Complexity – some journeys can be long and difficult to test, so ensuring key interactions between them can be time consuming and difficult to manage.
  2. Cost – although UTAM can help reduce maintenance burden, there is still an element of time and resource involved to write the test and continuously maintain them.
  3. Control – where a process or journey relies on external systems/services, these E2E tests are prone to failure due to updates outside of our control.

A Helpful Tool

You may find when writing these tests, it’s difficult to know which element in the DOM tree you should use, especially when you’re spoilt for choice using salesforce-pageobjects library.

UTAM Chrome extension helps highlight which page object / JS method is available on the page you’re viewing. The highlighting is similar to Chrome’s inspect element tool.

UTAM Chrome Extension

Here is a link to the official documentation on how to set up and use the Chrome extension.

UTAM also provides a generator, which allows you to dump in a HTML file and it will attempt to translate this to a JSON page object. This can be super useful to get a skeleton structure on how your page object should look, however, this generator does not take into account the usage of salesforce-pageobjects library.

For further information on UTAM generator, see the official documentation here.

Be Patient!

From my own experience of using UTAM and any E2E testing – patience is key. There will be a lot of trial and error, however, when you get a successful test run, that automation will provide you with more knowledge/experience for writing your next, more complex, test.

Philosophy, Challenges, and Opportunities of DevOps on Salesforce

 

DevOps on Salesforce is not simple. But why does it have to be so complex? If we go back to the beginning and think about the philosophy of DevOps, it reminds us of what we’re actually trying to achieve. It helps us to understand which scenarios demand layers of tools and processes, and which scenarios can be solved simply. It also highlights the opportunities: What can we gain by adopting these practices?

Continue reading “Philosophy, Challenges, and Opportunities of DevOps on Salesforce”