subtree updates

meta-raspberrypi: fde68b24f0..4c033eb074:
  Harunobu Kurokawa (1):
        rpi-cmdline, rpi-u-boot-src: Support USB boot

meta-arm: 0b61cc659a..4d22f982bc:
  Debbie Martin (2):
        arm-systemready: Add parted dependency and inherit testimage
        ci: Add Arm SystemReady firmware and IR ACS builds

  Harsimran Singh Tungal (3):
        arm-bsp/documentation: corstone1000: fix the steps in the user guide and instructions
        corstone1000:arm-bsp/optee: Update optee to v4.0
        corstone1000:arm-bsp/tftf: Fix tftf tests on mps3

  Jon Mason (5):
        arm/trusted-firmware-a: move patch file to bbappend
        arm/trusted-firmware-a: update to 2.10
        arm/hafnium: update to v2.10
        CI: rename meta-secure-core directory
        arm/edk2: update to 202311

  Ross Burton (1):
        CI: switch back to master

poky: 028b6f6226..4675bbb757:
  Adrian Freihofer (4):
        cmake-qemu.bbclass: make it more usable
        oe-selftest: add a cpp-example recipe
        oeqa/core/decorator: add skip if not qemu-usermode
        oe-selftest: add tests for C and C++ build tools

  Alassane Yattara (22):
        bitbake: toaster/test: bug-fix on tests/browser/test_all_builds_page
        bitbake: toaster/test: from test_no_builds_message.py wait for the empty state div to appear
        bitbake: toaster/test: delay driver action until elements to appear
        bitbake: toaster/tests: Ensure to kill toaster process create for tests functional
        bitbake: toaster/tests: Added functional/utils, contains useful methods using by functional tests
        bitbake: toaster/tests: Refactorize tests/functional
        bitbake: toaster/tests: Bug fixes, functional tests dependent on each other
        bitbake: toaster/tests: Fixes warnings in autobuilder
        bitbake: toaster/tests: bug-fix tests writing files into /tmp on the autobuilders
        bitbake: toaster/test: fix Copyright
        bitbake: toaster/tests: logging warning in console, trying to kill unavailable Runbuilds process
        bitbake: toaster/tests: Removed all time.sleep occurrence
        bitbake: toaster/tests: Bug-Fix testcase functional/test_project_page_tab_config.py
        bitbake: toaster/tests: bug-fix element click intercepted in browser/test_layerdetails_page.py
        bitbake: toaster/tests: Update tests/functional/functional_helpers test_functional_basic
        bitbake: toaster/tests: Fixes functional tests warning on autobuilder
        bitbake: toaster/tests: Bug-fix test_functional_basic, delay driver actions
        bitbake: toaster/tests: bug-fix An element matching "#projectstable" should be visible
        bitbake: toaster/tests: bug-fix An element matching "#lastest_builds" should be on the page
        bitbake: toaster/tests: Skip to show more then 100 item in ToasterTable
        bitbake: toaster/tests: Bug-fix "#project-created-notification" should be visible
        bitbake: toaster/toastergui: Bug-fix verify given layer path only if import/add local layer

  Alex Bennée (1):
        qemurunner: more cleanups for output blocking

  Alex Kiernan (17):
        cargo: Rename MANIFEST_PATH -> CARGO_MANIFEST_PATH
        cargo: Move CARGO_MANIFEST_PATH/CARGO_SRC_DIR to cargo_common
        rust: cargo: Convert single-valued variables to weak defaults
        cargo: Add CARGO_LOCK_PATH for path to Cargo.lock
        rust: Upgrade 1.70.0 -> 1.71.0
        rust: Upgrade 1.71.0 -> 1.71.1
        sstate-cache-management: Rewrite in python
        devtool: selftest: Fix test_devtool_modify_git_crates_subpath inequality
        devtool: selftest: Fix test_devtool_modify_git_crates_subpath bbappend check
        meta-selftest: hello-rs: Simple rust test recipe
        devtool: selftest: Swap to hello-rs for crates testing
        zvariant: Drop recipe
        rust: Upgrade 1.71.1 -> 1.72.0
        rust: Upgrade 1.72.0 -> 1.72.1
        rust: Upgrade 1.72.1 -> 1.73.0
        rust: Upgrade 1.73.0 -> 1.74.0
        rust: Upgrade 1.74.0 -> 1.74.1

  Alexander Kanavin (21):
        selftest/sstatetest: print output from bitbake with actual newlines, not \n
        selftest/sstatetests: do not delete custom $TMPDIRs under build-st when testing printdiff
        sstatesig/find_siginfo: special-case gcc-source when looking in sstate caches
        oeqa/selftest/sstatetests: re-work CDN tests, add local cache tests
        gobject-introspection: depend on setuptools to obtain distutils module
        libcap-ng-python: depend on setuptools to obtain distutils copy
        dnf: remove obsolete python3-gpg dependency (provided by gpgme)
        gpgme: disable python support (until upstream fixes 3.12 compatibility)
        python3-setuptools-rust: remove distutils dependency
        python3-babel: replace distutils with setuptools, as supported by upstream
        python3-pip: remove distutils depedency
        glib-2.0: replace distutils dependency with setuptools
        python3-pytest-runner: remove distutils dependency
        python3-numpy: distutils is no longer required
        bitbake: bitbake/codeparser.py: address ast module deprecations in py 3.12
        glibc-y2038-tests: do not run tests using 32 bit time APIs
        bitbake: bitbake/runqueue: add debugging for find_siginfo() calls
        bitbake: bitbake-diffsigs/runqueue: adapt to reworked find_siginfo()
        bitbake: bitbake/runqueue: prioritize local stamps over sstate signatures in printdiff
        sstatesig/find_siginfo: unify a disjointed API
        lib/sstatesig/find_siginfo: raise an error instead of returning None when obtaining mtime

  Alexander Lussier-Cullen (6):
        bitbake: toaster: fix pytest build test execution and test discovery
        bitbake: toaster: Add verbose printout for missing chrome(driver) dependencies
        bitbake: bitbake: toaster: add functional testing toaster error details
        bitbake: toaster/tests: Exit tests on chromedriver creation failure
        bitbake: toaster/tests: fix functional tests setup and teardown
        bitbake: toaster/tests: fix chrome argument syntax and wait for driver exit

  Alexandre Belloni (1):
        oeqa/selftest/recipetool: stop looking for md5sum

  Anuj Mittal (9):
        sqlite3: upgrade 3.44.0 -> 3.44.2
        base-passwd: upgrade 3.6.2 -> 3.6.3
        bluez5: upgrade 5.70 -> 5.71
        glib-2.0: upgrade 2.78.1 -> 2.78.3
        glib-networking: upgrade 2.76.1 -> 2.78.0
        puzzles: upgrade to latest revision
        stress-ng: upgrade 0.17.01 -> 0.17.03
        libusb1: fix upstream version check
        enchant2: upgrade 2.6.2 -> 2.6.4

  Archana Polampalli (1):
        bluez5: fix CVE-2023-45866

  Bruce Ashfield (31):
        linux-yocto/6.5: cfg: split runtime and symbol debug
        linux-yocto/6.5: update to v6.5.11
        linux-yocto/6.1: update to v6.1.62
        linux-yocto-dev: bump to v6.7
        linux-yocto/6.5: update to v6.5.12
        linux-yocto/6.5: update to v6.5.13
        linux-yocto/6.1: update to v6.1.65
        linux-yocto/6.1: drop removed IMA option
        linux-yocto/6.5: drop removed IMA option
        linux-yocto-rt/6.1: update to -rt18
        linux-yocto/6.1: update to v6.1.66
        linux-yocto/6.1: update to v6.1.67
        linux-yocto/6.5: fix AB-INT: QEMU kernel panic: No irq handler for vector
        linux-yocto/6.1: update to v6.1.68
        oeqa/runtime/parselogs: add qemux86 ACPI ignore for kernel v6.6+
        linux-libc-headers: update to v6.6-lts
        linux-yocto: introduce 6.6 reference kernel
        linux-yocto/6.6: fix AB-INT: QEMU kernel panic: No irq handler for vector
        linux-yocto-rt/6.6: fix CVE exclusion include
        linux-yocto/6.6: update CVE exclusions
        linux-yocto/6.6: update to v6.6.8
        linux-yocto/6.1: update to v6.1.69
        linux-yocto/6.5: drop 6.5 recipes
        linux-yocto-rt/6.6: correct meta data branch
        linux-yocto/6.6: update to v6.6.9
        linux-yocto/6.6: update CVE exclusions
        linux-yocto/6.1: update to v6.1.70
        linux-yocto/6.1: update CVE exclusions
        linux-yocto/6.6: ARM fix configuration audit warning
        linux-yocto/6.6: arm: jitter entropy backport
        poky/poky-tiny: make 6.6 the default kernel

  Changqing Li (1):
        man-pages: remove conflict pages

  Chen Qi (1):
        devtool: use straight print in check-upgrade-status output

  Clay Chang (1):
        devtool: deploy: provide max_process to strip_execs

  Daniel Ammann (1):
        base: Unpack .7z files with p7zip

  Deepthi Hemraj (1):
        autoconf: Add missing perl modules to RDEPENDS

  Dhairya Nagodra (2):
        cve-update-nvd2-native: faster requests with API keys
        cve-update-nvd2-native: increase the delay between subsequent request failures

  Eilís 'pidge' Ní Fhlannagáin (3):
        useradd: Fix issues with useradd dependencies
        useradd: Add testcase for bugzilla issue (currently disabled)
        usergrouptests.py: Add test for switching between static-ids

  Enrico Scholz (1):
        tcp-wrappers: drop libnsl2 build dependency

  Etienne Cordonnier (2):
        gdb/systemd: enable minidebuginfo support conditionally
        manuals: document minidebuginfo

  Fabio Estevam (3):
        libdrm: Upgrade to 2.4.119
        kmscube: Upgrade to latest revision
        bmap-tools: Upgrade to 3.7

  Hongxu Jia (2):
        socat: 1.7.4.4 -> 1.8.0.0
        man-db: 2.11.2 -> 2.12.0

  Jason Andryuk (3):
        linux-firmware: Package iwlwifi .pnvm files
        linux-firmware: Change bnx2 packaging
        linux-firmware: Create bnx2x subpackage

  Jeremy A. Puhlman (1):
        create-spdx-2.2: combine spdx can try to write before dir creation

  Jermain Horsman (2):
        lib/bblayers/makesetup.py: Remove unused imports
        lib/bblayers/buildconf.py: Remove unused imports/variables

  Jose Quaresma (2):
        go: update 1.20.10 -> 1.20.11
        go: update 1.20.11 -> 1.20.12

  Joshua Watt (11):
        bitbake: bitbake-hashserv: Add description of permissions
        bitbake.conf: Add runtimedir
        rpcbind: Specify state directory under /run
        libinput: Add packageconfig for tests
        ipk: Switch to using zstd compression
        lib/oe/path.py: Add relsymlink()
        lib/packagedata.py: Fix broken symlinks for providers with a '/'
        bitbake: contrib/vim: Syntax improvements
        classes-global/sstate: Fix variable typo
        lib/packagedata.py: Add API to iterate over rprovides
        classes-global/insane: Look up all runtime providers for file-rdeps

  Julien Stephan (19):
        recipetool: create_buildsys_python.py: initialize metadata
        recipetool: create: add trailing newlines
        recipetool: create: add new optional process_url callback for plugins
        recipetool: create_buildsys_python: add pypi support
        oeqa/selftest/recipetool: remove spaces on empty lines
        oeqa/selftest/recipetool/devtool: add test for pypi class
        recipetool: appendsrcfile(s): add dry-run mode
        recipeutils: bbappend_recipe: fix undefined variable
        recipeutils: bbappend_recipe: fix docstring
        recipeutils: bbappend_recipe: add a way to specify the name of the file to add
        recipeutils: bbappend_recipe: remove old srcuri entry if parameters are different
        recipetool: appendsrcfile(s): use params instead of extraline
        recipeutils: bbappend_recipe: allow to patch the recipe itself
        recipetool: appendsrcfile(s): add a mode to update the recipe itself
        oeqa/selftest/recipetool: appendsrfile: add test for machine
        oeqa/selftest/recipetool: appendsrc: add test for update mode
        oeqa/selftest/recipetool: add back checksum checks on pypi tests
        oeqa/selftest/recipetool: remove left over from development
        oeqa/selftest/recipetool: fix metadata corruption on meta layer

  Kevin Hao (2):
        beaglebone-yocto: Remove the redundant kernel-devicetree
        beaglebone-yocto: Remove the obsolete variables for uImage

  Khem Raj (13):
        tiff: Backport fixes for CVE-2023-6277
        kmod: Fix build with latest musl
        elfutils: Use own basename API implementation
        util-linux: Fix build with latest musl
        sysvinit: Include libgen.h for basename API
        attr: Fix build with latest musl
        opkg: Use own version of portable basename function
        util-linux: Delete md-raid tests
        gdb: Update to gdb 14.1 release
        systemd: Fix build with latest musl
        qemu: Fix build with latest musl
        qemu: Add packageconfig knob to enable pipewire support
        weston: Include libgen.h for basename

  Lee Chee Yang (5):
        migration-guides: reword fix in release-notes-4.3.1
        migration-guides: add release notes for 4.0.15
        perlcross: update to 1.5.2
        perl: 5.38.0 -> 5.38.2
        curl: update to 8.5.0

  Lucas Stach (1):
        mesa: upgrade 23.2.1 -> 23.3.1

  Ludovic Jozeau (1):
        image-live.bbclass: LIVE_ROOTFS_TYPE support compression

  Lukas Funke (1):
        selftest: wic: add test for zerorize option of empty plugin

  Malte Schmidt (1):
        wic: extend empty plugin with options to write zeros to partiton

  Markus Volk (3):
        gtk4: upgrade 4.12.3 -> 4.12.4
        libadwaita: update 1.4.0 -> 1.4.2
        appstream: Upgrade 0.16.3 -> 1.0.0

  Marlon Rodriguez Garcia (5):
        bitbake: toaster/tests: Update build test
        bitbake: toaster: Added new feature to import eventlogs from command line into toaster using replay functionality
        bitbake: toaster: remove test and update setup to avoid rebuilding image
        bitbake: toaster: Commandline build import table improvements
        bitbake: toaster: Added validation to stop import if there is a build in progress

  Marta Rybczynska (1):
        bitbake: toastergui: verify that an existing layer path is given

  Massimiliano Minella (1):
        zstd: fix LICENSE statement

  Michael Opdenacker (8):
        test-manual: text and formatting fixes
        test-manual: resource updates
        test-manual: use working example
        test-manual: add links to python unittest
        test-manual: explicit or fix file paths
        test-manual: add or improve hyperlinks
        dev-manual: runtime-testing: fix test module name
        poky.conf: update SANITY_TESTED_DISTROS to match autobuilder

  Mikko Rapeli (1):
        runqemu: match .rootfs. in addition to -image- for rootfs

  Ming Liu (1):
        grub: fs/fat: Don't error when mtime is 0

  Mingli Yu (2):
        python3-license-expression: Fix the ptest failure
        ptest-packagelists.inc: Add python3-license-expression

  Pavel Zhukov (2):
        bitbake: utils: Do not create directories with ${ in the name
        oeqa/selftest/bbtests: Add test for unexpanded variables in the dirname

  Peter Kjellerstedt (11):
        oeqa/selftest/devtool: Correct git clone of local repository
        oeqa/selftest/devtool: Avoid global Git hooks when amending a patch
        oeqa/selftest/devtool: Make test_devtool_load_plugin more resilient
        oeqa/selftest/recipetool: Make test_recipetool_load_plugin more resilient
        lib/oe/recipeutils: Avoid wrapping any SRC_URI[sha*sum] variables
        recipetool: create: Improve identification of licenses
        recipetool: create: Only include the expected SRC_URI checksums
        devtool: upgrade: Update all existing checksums for the SRC_URI
        devtool: modify: Make --no-extract work again
        devtool: modify: Handle recipes with a menuconfig task correctly
        dev-manual: Discourage the use of SRC_URI[md5sum]

  Peter Marko (1):
        dtc: preserve version also from shallow git clones

  Philip Balister (1):
        sanity.bbclass: Check for additional native perl modules.

  Renat Khalikov (1):
        python3-maturin: Add missing space appending to CFLAGS

  Richard Purdie (41):
        bitbake: runqueue: Improve inter setscene task dependency handling
        bitbake: bb/toaster: Fix assertEquals deprecation warnings
        bitbake: toaster: Fix assertRegexpMatches deprecation warnings
        bitbake: toastermain/settings: Avoid python filehandle closure warnings
        bitbake: toastergui: Fix regex markup issues
        bitbake: bitbake: Move to version 2.6.1 to mark runqueue changes
        bitbake: toaster-eventreplay: Remove ordering assumptions
        sanity.conf: Require bitbake 2.6.1 for recent runqueue change
        sstate: Remove unneeded code from setscene_depvalid() related to useradd
        oeqa/runtime/systemd: Ensure test runs only on systemd images
        bitbake: toaster: Update to use qemux86-64 machine by default
        bitbake: toaster/tests/builds: Add BB_HASHSERVE passthrough
        pseudo: Update to pull in syncfs probe fix
        useradd: Fix useradd do_populate_sysroot dependency bug
        sstate: Fix dir ownership issues in SSTATE_DIR
        oeqa/sstatetests: Disable gcc source printdiff test for now
        build-appliance-image: Update to master head revision
        bitbake: utils: Fix mkdir with PosixPath
        bitbake: runqueue: Remove tie between rqexe and starts_worker
        build-appliance-image: Update to master head revision
        testimage: Exclude wtmp from target-dumper commands
        qemurunner: Improve stdout logging handling
        qemurunner: Improve handling of serial port output blocking
        oeqa/selftest/overlayfs: Don't overwrite DISTRO_FEATURES
        testimage: Drop target_dumper and most of monitor_dumper
        oeqa/selftest/overlayfs: Fix whitespace
        qemu: Clean up DEPENDS
        qemu: Ensure pip and the python venv aren't used for meson
        curl: Disable two intermittently failing tests
        linux/cve-exclusion6.1: Update to latest kernel point release
        lib/prservice: Improve lock handling robustness
        oeqa/selftest/prservice: Improve test robustness
        scripts: Drop shell sstate-cache-management
        oeqa/selftest/sstatetests: Update sstate management script tests to python script
        curl: Disable test 1091 due to intermittent failures
        bitbake: lib/bb: Add workaround for libgcc issues with python 3.8 and 3.9
        bitbake: bitbake: Post release version bump to 2.7.0
        bitbake: siggen: Ensure version of siggen is verified
        bitbake: bitbake: Version bump for find_siginfo chanages
        sstatesig: Add version information for find_sigingfo
        sanity: Require bitbake 2.7.1

  Robert Berger (1):
        uninative-tarball.xz - reproducibility fix

  Robert Yang (5):
        gettext: Upgrade 0.22.3 -> 0.22.4
        nfs-utils: Upgrade 2.6.3 -> 2.6.4
        archiver.bbclass: Improve work-shared checking
        nfs-utils: Update Upstream-Status
        archiver.bbclass: Drop tarfile module to improve performance

  Ross Burton (23):
        avahi: update URL for new project location
        oeqa/runtime/parselogs: load ignores from disk
        oeqa/runtime/parselogs: migrate ignores
        meta-yocto-bsp/oeqa/parselogs: add BSP-specific ignores
        linux-yocto: update CVE exclusions
        genericx86: remove redundant assignments
        images: remove redundant IMAGE_BASENAME assignments
        insane: ensure more paths have the workdir removed
        tcl: skip timing-dependent tests in run-ptest
        qemurunner: remove unused import
        go: set vendor in CVE_PRODUCT
        runqemu: add qmp socket support
        linux-yocto: update CVE exclusions
        tcl: skip async and event tests in run-ptest
        images: add core-image-initramfs-boot
        machine/arch-armv9: remove crc and sve tunes, they are mandatory
        python3: re-enable profile guided optimisation
        openssl: mark assembler sections as call targets for PAC/BTI support on aarch64
        nativesdk: ensure features don't get backfilled
        nativesdk: don't unset MACHINE_FEATURES, let machine-sdk/ set it
        conf/machine-sdk: declare qemu-usermode SDK_MACHINE_FEATURE
        libseccomp: remove redundant PV assignment
        oeqa/parselogs-ignores-qemuarmv5: add comments and organise

  Saul Wold (1):
        package.py: OEHasPackage: Add MLPREFIX to packagename

  Shubham Kulkarni (1):
        tzdata: Upgrade to 2023d

  Simone Weiß (2):
        manuals: brief-yoctoprojectqs: align variable order with default local.conf
        patchtest: Add test for deprecated CVE_CHECK_IGNORE

  Soumya Sambu (1):
        ncurses: Fix - tty is hung after reset

  Sundeep KOKKONDA (1):
        rust: rustdoc reproducibility issue fix - disable PGO

  Tim Orling (12):
        python3-bcrypt: upgrade 4.0.1 -> 4.1.1
        python3-pygments: upgrade 2.16.1 -> 2.17.2
        recipetool: pypi: do not clobber SRC_URI checksums
        python3-setuptools-rust: BBCLASSEXTEND + nativesdk
        python3-maturin: add v1.4.0
        python3-maturin: bzip2-sys reproduciblility
        classes-recipe: add python_maturin.bbclass
        recipetool: add python_maturin support
        oe-selfest: add maturn runtime (testimage) test
        oeqa: add simple 'maturin' SDK (testsdk) test case
        oeqa: add "maturin develop" SDK test case
        oeqa: add runtime 'maturin develop' test case

  Tom Rini (1):
        inetutils: Update to the 2.5 release

  Trevor Gamblin (1):
        scripts/runqemu: fix regex escape sequences

  Victor Kamensky (5):
        systemtap: upgrade 4.9 -> 5.0
        systemtap: do not install uprobes and uprobes sources
        systemtap-uprobes: removed as obsolete
        systemtap: explicit handling debuginfod library dependency
        systemtap: fix libdebuginfod auto detection logic

  Vijay Anusuri (1):
        avahi: backport CVE-2023-1981 & CVE's follow-up patches

  Viswanath Kraleti (2):
        image-uefi.conf: Add EFI_UKI_PATH variable
        systemd-boot: Add recipe to compile native

  Wang Mingyu (38):
        kbd: upgrade 2.6.3 -> 2.6.4
        libatomic-ops: upgrade 7.8.0 -> 7.8.2
        libnl: upgrade 3.8.0 -> 3.9.0
        libseccomp: upgrade 2.5.4 -> 2.5.5
        libva-utils: upgrade 2.20.0 -> 2.20.1
        dnf: upgrade 4.18.1 -> 4.18.2
        gpgme: upgrade 1.23.1 -> 1.23.2
        kea: upgrade 2.4.0 -> 2.4.1
        opkg-utils: upgrade 0.6.2 -> 0.6.3
        repo: upgrade 2.39 -> 2.40
        sysstat: upgrade 12.7.4 -> 12.7.5
        p11-kit: upgrade 0.25.2 -> 0.25.3
        python3-babel: upgrade 2.13.1 -> 2.14.0
        python3-dbusmock: upgrade 0.29.1 -> 0.30.0
        python3-hatchling: upgrade 1.18.0 -> 1.20.0
        python3-hypothesis: upgrade 6.90.0 -> 6.92.1
        python3-importlib-metadata: upgrade 6.8.0 -> 7.0.0
        python3-license-expression: upgrade 30.1.1 -> 30.2.0
        python3-pathspec: upgrade 0.11.2 -> 0.12.1
        python3-pip: upgrade 23.3.1 -> 23.3.2
        python3-psutil: upgrade 5.9.6 -> 5.9.7
        python3-pytest-runner: upgrade 6.0.0 -> 6.0.1
        python3-trove-classifiers: upgrade 2023.11.22 -> 2023.11.29
        python3-typing-extensions: upgrade 4.8.0 -> 4.9.0
        python3-wcwidth: upgrade 0.2.11 -> 0.2.12
        ttyrun: upgrade 2.29.0 -> 2.30.0
        xwayland: upgrade 23.2.2 -> 23.2.3
        diffoscope: upgrade 252 -> 253
        iputils: upgrade 20221126 -> 20231222
        gstreamer1.0: upgrade 1.22.7 -> 1.22.8
        dhcpcd: upgrade 10.0.5 -> 10.0.6
        fontconfig: upgrade 2.14.2 -> 2.15.0
        python3-setuptools: upgrade 69.0.2 -> 69.0.3
        python3-dbusmock: upgrade 0.30.0 -> 0.30.1
        python3-hatchling: upgrade 1.20.0 -> 1.21.0
        python3-importlib-metadata: upgrade 7.0.0 -> 7.0.1
        python3-lxml: upgrade 4.9.3 -> 4.9.4
        aspell: upgrade 0.60.8 -> 0.60.8.1

  Yash Shinde (1):
        rust: Disable rust oe-selftest

  Yi Zhao (3):
        json-glib: upgrade 1.6.6 -> 1.8.0
        psplash: upgrade to latest revision
        debianutils: upgrade 5.14 -> 5.15

  Yoann Congal (2):
        lib/oe/patch: handle creating patches for CRLF sources
        strace: Disable bluetooth support by default

  Zang Ruochen (2):
        ell: upgrade 0.60 -> 0.61
        musl: add typedefs for Elf64_Relr and Elf32_Relr

  Zoltan Boszormenyi (1):
        update_gtk_icon_cache: Fix for GTK4-only builds

  venkata pyla (1):
        wic: use E2FSPROGS_FAKE_TIME and hash_seed to generate reproducible ext4 images

meta-openembedded: 5ad7203f68..7d8115d550:
  Alex Kiernan (7):
        mdns: Fix HOMEPAGE URL
        mbedtls: Upgrade 3.5.0 -> 3.5.1
        c-ares: Upgrade 1.22.1 -> 1.24.0
        mdns: Upgrade 2200.40.37.0.1 -> 2200.60.25.0.4
        c-ares: Move to tarballs, add ptest and static support
        thin-provisioning-tools: Upgrade 1.0.4 -> 1.0.9
        bearssl: Upgrade to latest

  Alexander Kanavin (29):
        python3-pyinotify: remove as unmaintained
        python3-supervisor: do not rely on smtpd module
        python3-meld3: do not rely on smtpd module
        python3-m2crypto: do not rely on smtpd module
        python3-uinput: remove as unmaintained
        python3-mcrypto: rely on setuptools for distutils copy
        python3-joblib: do not rely in distutils
        python3-web3: remove distutils dependency
        python3-cppy: remove unused distutils dependency
        python3-pyroute2: remove unused distutils dependency
        python3-eventlet: backport a patch to remove distutils dependency
        python3-unoconv: rely on setuptools to obtain distutils copy
        python3-astroid: remove unneeded distutils dependency
        python3-django: remove unneeded distutils dependency
        python3-pillow: remove unneeded distutils dependency
        python3-grpcio: update 1.56.2 -> 1.59.3
        gstd: correctly delete files in do_install
        libplist: fix python 3.12 compatibility
        libcamera: skip until upstream resolves python 3.12 compatibility
        nodejs: backport (partially) python 3.12 support
        nodejs: backport (partially) python 3.12 support
        polkit: remove long obsolete 0.119 version
        mozjs-115: split the way-too-long PYTHONPATH line
        polkit: update mozjs dependency 102 -> 115
        mozjs-115: backport py 3.12 compatibility
        mozjs-102: remove the recipe
        gthumb: update 3.12.2 -> 3.12.4
        flatpak: do not rely on executables from the host
        bolt: package systemd units

  Archana Polampalli (1):
        cjson: upgrade 1.7.16 -> 1.7.17

  Bruce Ashfield (1):
        zfs: update to 2.2.2

  Changqing Li (2):
        postgresql: upgrade 15.4 -> 15.5
        redis: upgrade 6.2.13 -> 6.2.14

  Derek Straka (70):
        python3-greenlet: update to version 3.0.2
        python3-ujson: update to version 5.9.0
        python3-termcolor: update to version 2.4.0
        python3-cmake: update to version 3.28.0
        python3-pint: upgrade to 0.23
        python3-gnupg: update to 0.5.2
        python3-pyzmq: update to 25.1.2
        python3-tox: update to version 4.11.4
        python3-olefile: update to version 0.47
        python3-distlib: update to version 0.3.8
        python3-colorlog: update to version 6.8.0
        python3-pymongo: update version to 4.6.1
        python3-bandit: update to version 1.7.6
        python3-gmqtt: update to version 0.6.13
        python3-portion: update to version 2.4.2
        python3-prompt-toolkit: update to version 3.0.43
        python3-asyncinotify: update to version 4.0.4
        python3-bitstring: update to version 4.1.4
        python3-ipython: update to version 8.18.1
        nginx: update versions for both the stable branch and mainline
        python3-portalocker: update to version 2.8.2
        python3-astroid: update to version 3.0.2
        python3-alembic: update to version 1.13.1
        python3-pymisp: update to verion 2.4.182
        python3-ninja: update to version 1.11.1.1
        python3-coverage: update to version 7.3.4
        python3-pdm: update to version 2.11.1
        python3-paramiko: update to version 3.4.0
        python3-zeroconf: update to version 0.131.0
        python3-wtforms: update to version 3.1.1
        python3-isort: update to version 5.13.2
        python3-protobuf: update to version 4.25.1
        python3-lazy-object-proxy: update to version 1.10.0
        python3-cantools: update to version 39.4.0
        python3-sentry-sdk: update to version 1.39.1
        python3-xmlschema: update to version 2.5.1
        python3-apiflask: update to version 2.1.0
        python3-rapidjson: update to version 1.14
        python3-bitarray: update to version 2.9.0
        python3-pyfanotify: update to version 0.2.2
        python3-eventlet: update to version 0.34.1
        python3-flask-wtf: update to version 1.2.1
        python3-grpcio: update to version 1.60.0
        python3-grpcio-tools: update to version 1.60.0
        python3-cmake: update to version 3.28.1
        python3-flask-sqlalchemy: fix upstream uri check
        python3-wtforms: fix upstream uri and version check
        gyp: update to the latest commit
        python3-ipython-genutils: fix upstream uri and version check
        python3-flask: fix upstream uri and version check
        python3-wpa-supplicant: fix upstream uri and version check
        python3-uswid: update to version 0.4.7
        python3-flask-wtf: fix upstream uri and version check
        python3-gspread: update to version 5.12.3
        python3-pytest-html: update to version 4.1.1
        python3-setuptools-scm-git-archive: remove obsolete package
        python3-pyroute2: update to version 0.7.10
        python3-constantly: update to version 23.10.4
        python3-mypy: update to version 1.8.0
        python3-flask-jwt-extended: update to version 4.6.0
        python3-greenlet: update to version 3.0.3
        python3-web3: update to version 6.13.0
        python3-parse: update to version 1.20.0
        python3-kmod: add comment about update to version 0.9.2
        python3-engineio: update to version 4.8.1
        python3-sqlalchemy: update to version 2.0.24
        python3-pdm-backend: update to version 2.1.8
        python3-cantools: update to version 39.4.1
        python3-argh: update to version 0.30.5
        python3-dominate: update to version 2.9.1

  Dmitry Baryshkov (2):
        android-tools: remove two Debianisms
        networkmanager: drop libnewt dependency

  Frederic Martinsons (3):
        crash: factorize recipe with inc file to prepare cross-canadian version
        crash: add cross canadian version
        crash: update to 8.0.4

  Jan Vermaete (1):
        netdata: added Python as rdepends

  Jean-Marc BOUCHE (1):
        terminus-font: build compressed archives with -n

  Jose Quaresma (1):
        ostree: Upgrade 2023.7 -> 2023.8

  Joshua Watt (1):
        redis: Create state directory in systemd service

  Jörg Sommer (1):
        i2cdev: New recipe with i2c tools

  Kai Kang (1):
        lvm2: 2.03.16 -> 2.03.22

  Khem Raj (3):
        Revert "nodejs: backport (partially) python 3.12 support"
        Revert "libcamera: skip until upstream resolves python 3.12 compatibility"
        libcamera: Fix build with python 3.12

  Leon Anavi (11):
        sip: Upgrade 6.7.12 -> 6.8.0
        python3-expandvars: add recipe
        python3-frozenlist: upgrade 1.4.0 -> 1.4.1
        python3-yarl: upgrade 1.9.2 -> 1.9.4
        python3-coverage: upgrade 7.3.2 -> 7.3.3
        python3-cycler: upgrade 0.11.0 -> 0.12.1
        python3-aiohue: upgrade 4.6.2 -> 4.7.0
        python3-sdbus: upgrade 0.11.0 -> 0.11.1
        python3-zeroconf: upgrade 0.128.4 -> 0.130.0
        python3-dominate: upgrade 2.8.0 -> 2.9.0
        python3-rlp: upgrade 3.0.0 -> 4.0.0

  Marek Vasut (1):
        faad2: Upgrade 2.10.0 -> 2.11.1

  Markus Volk (3):
        wireplumber: update 0.4.15 -> 0.4.17
        tracker: dont inherit gsettings
        gnome-software: update 45.1 -> 45.2

  Martin Jansa (4):
        monocypher: pass LIBDIR to fix installed-vs-shipped QA issue with multilib
        rygel: fix build with gtk+3 PACKAGECONFIG disabled
        rygel: add x11 to DISTRO_FEATURES
        driverctl: fix installed-vs-shipped

  Meenali Gupta (1):
        nginx: upgrade 1.25.2 -> 1.25.3

  Mingli Yu (2):
        mariadb: Upgrade to 10.11.6
        tk: Remove buildpath issue

  Nathan BRIENT (1):
        cyaml: new recipe

  Niko Mauno (1):
        pkcs11-provider: Add recipe

  Ny Antra Ranaivoarison (1):
        python3-click-spinner: backport patch that fixes deprecated methods

  Patrick Wicki (1):
        poco: upgrade 1.12.4 -> 1.12.5p2

  Petr Chernikov (1):
        abseil-cpp: remove -Dcmake_cxx_standard=14 flag from extra_oecmake

  Robert Yang (1):
        minifi-cpp: Fix do_configure error builder aarch64

  Ross Burton (13):
        Remove unused SRC_DISTRIBUTE_LICENSES
        gspell: inherit gtk-doc
        gspell: update DEPENDS, switch iso-codes for icu
        librest: remove spurious build dependencies
        librest: inherit gtk-doc
        keybinder: use autotools-brokensep instead of setting B
        keybinder: disable gtk-doc documentation
        gtksourceview3: remove obsolete DEPENDS
        libgsf: remove obsolete DEPENDS
        evolution-data-server: remove obsolete intltool DEPENDS
        php: remove lemon-native build dependency
        lemon: upgrade to 3.44.2
        renderdoc: no need to depend on vim-native

  Samuli Piippo (1):
        jasper: enable opengl only wih x11

  Theodore A. Roth (1):
        python3-flask-sqlalchemy: upgrade 2.5.1 -> 3.1.1

  Thomas Perrot (2):
        networkmanager: add missing modemmanager rdepends
        networkmanager: fix some missing pkgconfig

  Tim Orling (8):
        python3-pydantic-core: add v2.14.5
        python3-annotated-types: add v0.6.0
        python3-pydantic: fix RDEPENDS
        python3-dirty-equals: add v0.7.1
        python3-pydantic-core: enable ptest
        python3-cloudpickle: add v3.0.0
        python3-pydantic: enable ptest
        python3-yappi: upgrade 1.4.0 -> 1.6.0; fix ptests

  Wang Mingyu (61):
        python3-alembic: upgrade 1.12.1 -> 1.13.0
        python3-ansi2html: upgrade 1.8.0 -> 1.9.1
        python3-argcomplete: upgrade 3.1.6 -> 3.2.1
        python3-dbus-fast: upgrade 2.15.0 -> 2.21.0
        python3-django: upgrade 4.2.7 -> 5.0
        python3-flask-restx: upgrade 1.2.0 -> 1.3.0
        python3-google-api-core: upgrade 2.14.0 -> 2.15.0
        python3-google-api-python-client: upgrade 2.108.0 -> 2.111.0
        python3-googleapis-common-protos: upgrade 1.61.0 -> 1.62.0
        python3-google-auth: upgrade 2.23.4 -> 2.25.2
        python3-imageio: upgrade 2.33.0 -> 2.33.1
        python3-isort: upgrade 5.12.0 -> 5.13.1
        python3-path: upgrade 16.7.1 -> 16.9.0
        python3-platformdirs: upgrade 4.0.0 -> 4.1.0
        python3-pytest-asyncio: upgrade 0.22.0 -> 0.23.2
        python3-sentry-sdk: upgrade 1.37.1 -> 1.39.0
        python3-bitarray: upgrade 2.8.3 -> 2.8.5
        python3-eth-keyfile: upgrade 0.6.1 -> 0.7.0
        python3-eth-rlp: upgrade 0.3.0 -> 1.0.0
        python3-fastnumbers: upgrade 5.0.1 -> 5.1.0
        python3-pylint: upgrade 3.0.2 -> 3.0.3
        python3-tornado: upgrade 6.3.3 -> 6.4
        python3-traitlets: upgrade 5.13.0 -> 5.14.0
        python3-types-setuptools: upgrade 68.2.0.2 -> 69.0.0.0
        python3-virtualenv: upgrade 20.24.7 -> 20.25.0
        python3-web3: upgrade 6.11.3 -> 6.12.0
        python3-websocket-client: upgrade 1.6.4 -> 1.7.0
        python3-zeroconf: upgrade 0.127.0 -> 0.128.4
        ctags: upgrade 6.0.20231126.0 -> 6.0.20231210.0
        gensio: upgrade 2.8.0 -> 2.8.2
        hwdata: upgrade 0.376 -> 0.377
        lvgl: upgrade 8.3.10 -> 8.3.11
        gjs: upgrade 1.78.0 -> 1.78.1
        ifenslave: upgrade 2.13 -> 2.14
        libei: upgrade 1.1.0 -> 1.2.0
        pkcs11-helper: upgrade 1.29.0 -> 1.30.0
        strongswan: upgrade 5.9.12 -> 5.9.13
        webkitgtk3: upgrade 2.42.2 -> 2.42.3
        sip: upgrade 6.8.0 -> 6.8.1
        paho-mqtt-cpp: upgrade 1.3.1 -> 1.3.2
        dbus-cxx: upgrade 2.4.0 -> 2.5.0
        exiftool: upgrade 12.70 -> 12.71
        uftp: upgrade 5.0.2 -> 5.0.3
        ctags: upgrade 6.0.20231210.0 -> 6.0.20231224.0
        jasper: Fix install conflict when enable multilib.
        jq: upgrade 1.7 -> 1.7.1
        libmbim: upgrade 1.31.1 -> 1.31.2
        libqmi: upgrade 1.34.0 -> 1.35.1
        opencl-headers: upgrade 2023.04.17 -> 2023.12.14
        valijson: upgrade 1.0.1 -> 1.0.2
        python3-apispec: upgrade 6.3.0 -> 6.3.1
        python3-asyncinotify: upgrade 4.0.4 -> 4.0.5
        python3-bitarray: upgrade 2.9.0 -> 2.9.1
        python3-cassandra-driver: upgrade 3.28.0 -> 3.29.0
        python3-ipython: upgrade 8.18.1 -> 8.19.0
        python3-pydantic: upgrade 2.5.2 -> 2.5.3
        python3-regex: upgrade 2023.10.3 -> 2023.12.25
        opencl-icd-loader: upgrade 2023.04.17 -> 2023.12.14
        python3-distro: upgrade 1.8.0 -> 1.9.0
        zchunk: upgrade 1.3.2 -> 1.4.0
        python3-eventlet: upgrade 0.34.1 -> 0.34.2

  William Lyu (1):
        networkmanager: Improved SUMMARY and added DESCRIPTION

  Xiangyu Chen (1):
        layer.conf: add libbpf to NON_MULTILIB_RECIPES

  Yi Zhao (2):
        open-vm-tools: upgrade 12.1.5 -> 12.3.5
        samba: upgrade 4.18.8 -> 4.18.9

  Zoltán Böszörményi (2):
        mutter: Make gnome-desktop and libcanberra dependencies optional
        zenity: Upgrade to 4.0.0

  alperak (29):
        jasper: upgrade 2.0.33 -> 4.1.1
        xcursorgen: upgrade 1.0.7 -> 1.0.8
        xstdcmap: upgrade 1.0.4 -> 1.0.5
        xlsclients: upgrade 1.1.4 -> 1.1.5
        xlsatoms: upgrade 1.1.3 -> 1.1.4
        xkbevd: upgrade 1.1.4 -> 1.1.5
        xgamma: upgrade 1.0.6 -> 1.0.7
        sessreg: upgrade 1.1.2 -> 1.1.3
        xbitmaps: upgrade 1.1.2 -> 1.1.3
        xcursor-themes: add recipe
        xorg-docs: add recipe
        xorg-sgml-doctools: update summary depends and inc file
        xf86-video-ati: upgrade 19.1.0 -> 22.0.0
        xf86-input-void: upgrade 1.4.1 -> 1.4.2
        libxaw: upgrade 1.0.14 -> 1.0.15
        xf86-video-mga: upgrade 2.0.0 -> 2.0.1
        snappy: upgrade 1.1.9 -> 1.1.10
        xsetroot: upgrade 1.1.2 -> 1.1.3
        libbytesize: Removed unnecessary setting of B
        libmxml: use autotools-brokensep instead of setting B
        libsombok3: use autotools-brokensep instead of setting B
        pgpool2: use autotools-brokensep instead of setting B
        qpdf: upgrade 11.6.3 -> 11.6.4
        cpprest: upgrade 2.10.18 -> 2.10.19
        avro-c: upgrade 1.11.2 -> 1.11.3
        dool: upgrade 1.1.0 -> 1.3.1
        driverctl: upgrade 0.111 -> 0.115
        hstr: upgrade 2.5.0 -> 3.1.0
        libharu: upgrade 2.3.0 -> 2.4.4

meta-security: 070a1e82cc..b2e1511338:
  Armin Kuster (6):
        libgssglue: update to 0.8
        python3-privacyidea: Update to 3.9.1
        lynis: Update SRC_URI to improve updater
        layers: Move READMEs to markdown format
        arpwatch: adjust CONFIGURE params to allow to build again.
        python3-pyinotify: fail2ban needs this module

  Dawid Dabrowski (1):
        libhoth recipe update

  Erik Schilling (2):
        dm-verity-img.bbclass: use bc-native
        dm-verity-img.bbclass: remove IMAGE_NAME_SUFFIX

  Mikko Rapeli (2):
        tpm2-tss: support native builds
        dm-verity-img.bbclass: add DM_VERITY_DEPLOY_DIR

Change-Id: I94d7f1ee5ff2da4555c05fbf63a1293ec8f249c2
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py b/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
index b80d403..7c20437 100644
--- a/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
+++ b/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
@@ -11,7 +11,6 @@
 import logging
 import subprocess
 import signal
-import time
 import re
 
 from tests.browser.selenium_helpers_base import SeleniumTestCaseBase
@@ -19,26 +18,48 @@
 from selenium.common.exceptions import NoSuchElementException
 
 logger = logging.getLogger("toaster")
+toaster_processes = []
 
 class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
-    wait_toaster_time = 5
+    wait_toaster_time = 10
 
     @classmethod
     def setUpClass(cls):
         # So that the buildinfo helper uses the test database'
         if os.environ.get('DJANGO_SETTINGS_MODULE', '') != \
             'toastermain.settings_test':
-            raise RuntimeError("Please initialise django with the tests settings:  " \
+            raise RuntimeError("Please initialise django with the tests settings:  "
                 "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
 
+        # Wait for any known toaster processes to exit
+        global toaster_processes
+        for toaster_process in toaster_processes:
+            try:
+                os.waitpid(toaster_process, os.WNOHANG)
+            except ChildProcessError:
+                pass
+
         # start toaster
         cmd = "bash -c 'source toaster start'"
-        p = subprocess.Popen(
+        start_process = subprocess.Popen(
             cmd,
             cwd=os.environ.get("BUILDDIR"),
             shell=True)
-        if p.wait() != 0:
-            raise RuntimeError("Can't initialize toaster")
+        toaster_processes = [start_process.pid]
+        if start_process.wait() != 0:
+            port_use = os.popen("lsof -i -P -n | grep '8000 (LISTEN)'").read().strip()
+            message = ''
+            if port_use:
+                process_id = port_use.split()[1]
+                process = os.popen(f"ps -o cmd= -p {process_id}").read().strip()
+                message = f"Port 8000 occupied by {process}"
+            raise RuntimeError(f"Can't initialize toaster. {message}")
+
+        builddir = os.environ.get("BUILDDIR")
+        with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f:
+            toaster_processes.append(int(f.read()))
+        with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f:
+            toaster_processes.append(int(f.read()))
 
         super(SeleniumFunctionalTestCase, cls).setUpClass()
         cls.live_server_url = 'http://localhost:8000/'
@@ -47,22 +68,30 @@
     def tearDownClass(cls):
         super(SeleniumFunctionalTestCase, cls).tearDownClass()
 
-        # XXX: source toaster stop gets blocked, to review why?
-        # from now send SIGTERM by hand
-        time.sleep(cls.wait_toaster_time)
-        builddir = os.environ.get("BUILDDIR")
+        global toaster_processes
 
-        with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f:
-            toastermain_pid = int(f.read())
-            os.kill(toastermain_pid, signal.SIGTERM)
-        with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f:
-            runbuilds_pid = int(f.read())
-            os.kill(runbuilds_pid, signal.SIGTERM)
+        cmd = "bash -c 'source toaster stop'"
+        stop_process = subprocess.Popen(
+            cmd,
+            cwd=os.environ.get("BUILDDIR"),
+            shell=True)
+        # Toaster stop has been known to hang in these tests so force kill if it stalls
+        try:
+            if stop_process.wait(cls.wait_toaster_time) != 0:
+                raise Exception('Toaster stop process failed')
+        except Exception as e:
+            if e is subprocess.TimeoutExpired:
+                print('Toaster stop process took too long. Force killing toaster...')
+            else:
+                print('Toaster stop process failed. Force killing toaster...')
+            stop_process.kill()
+            for toaster_process in toaster_processes:
+                os.kill(toaster_process, signal.SIGTERM)
 
 
     def get_URL(self):
          rc=self.get_page_source()
-         project_url=re.search("(projectPageUrl\s:\s\")(.*)(\",)",rc)
+         project_url=re.search(r"(projectPageUrl\s:\s\")(.*)(\",)",rc)
          return project_url.group(2)
 
 
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py b/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
index dc7d1fc..9f88010 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
@@ -16,6 +16,7 @@
 
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestCreateNewProject(SeleniumFunctionalTestCase):
 
     def _create_test_new_project(
@@ -48,7 +49,7 @@
 
         self.driver.find_element(By.ID, "create-project-button").click()
 
-        element = self.wait_until_visible('#project-created-notification')
+        element = self.wait_until_visible('#project-created-notification', poll=3)
         self.assertTrue(
             self.element_exists('#project-created-notification'),
             f"Project:{project_name} creation notification not shown"
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py b/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
index f558cce..e4070fb 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
@@ -7,97 +7,111 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
-import re, time
+import re
 from django.urls import reverse
 import pytest
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from orm.models import Project
 from selenium.webdriver.common.by import By
 
+from tests.functional.utils import get_projectId_from_url
 
-@pytest.mark.order("last")
+
+@pytest.mark.django_db
+@pytest.mark.order("second_to_last")
 class FuntionalTestBasic(SeleniumFunctionalTestCase):
+    """Basic functional tests for Toaster"""
+    project_id = None
+
+    def setUp(self):
+        super(FuntionalTestBasic, self).setUp()
+        if not FuntionalTestBasic.project_id:
+            self._create_slenium_project()
+            current_url = self.driver.current_url
+            FuntionalTestBasic.project_id = get_projectId_from_url(current_url)
 
 #   testcase (1514)
-    @pytest.mark.django_db
-    def test_create_slenium_project(self):
+    def _create_slenium_project(self):
         project_name = 'selenium-project'
         self.get(reverse('newproject'))
+        self.wait_until_visible('#new-project-name', poll=3)
         self.driver.find_element(By.ID, "new-project-name").send_keys(project_name)
         self.driver.find_element(By.ID, 'projectversion').click()
         self.driver.find_element(By.ID, "create-project-button").click()
-        time.sleep(2)
-        element = self.wait_until_visible('#project-created-notification')
+        element = self.wait_until_visible('#project-created-notification', poll=10)
         self.assertTrue(self.element_exists('#project-created-notification'),'Project creation notification not shown')
         self.assertTrue(project_name in element.text,
                         "New project name not in new project notification")
         self.assertTrue(Project.objects.filter(name=project_name).count(),
                         "New project not found in database")
+        return Project.objects.last().id
 
  #  testcase (1515)
     def test_verify_left_bar_menu(self):
         self.get(reverse('all-projects'))
-        self.wait_until_visible('#projectstable')
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
         self.assertTrue(self.element_exists('#config-nav'),'Configuration Tab does not exist')
         project_URL=self.get_URL()
         self.driver.find_element(By.XPATH, '//a[@href="'+project_URL+'"]').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'customimages/"'+"]").click()
-            time.sleep(2)
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Custom images",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'Custom images information is not loading properly')
         except:
             self.fail(msg='No Custom images tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'images/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible image recipes information is not loading properly')
         except:
             self.fail(msg='No Compatible image tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'softwarerecipes/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible software recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible software recipe information is not loading properly')
         except:
             self.fail(msg='No Compatible software recipe tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'machines/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible machines",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible machine information is not loading properly')
         except:
             self.fail(msg='No Compatible machines tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'layers/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible layers",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible layer information is not loading properly')
         except:
             self.fail(msg='No Compatible layers tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'configuration"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Bitbake variables",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Bitbake variables information is not loading properly')
         except:
             self.fail(msg='No Bitbake variables tab available')
 
 #   testcase (1516)
     def test_review_configuration_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
         project_URL=self.get_URL()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
         try:
            self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
-           self.assertTrue(re.search("qemux86",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
+           self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
            self.driver.find_element(By.XPATH, "//span[@id='change-machine-toggle']").click()
-           time.sleep(2)
-           self.wait_until_visible('#select-machine-form')
-           self.wait_until_visible('#cancel-machine-change')
+           self.wait_until_visible('#select-machine-form', poll=10)
+           self.wait_until_visible('#cancel-machine-change', poll=10)
            self.driver.find_element(By.XPATH, "//form[@id='select-machine-form']/a[@id='cancel-machine-change']").click()
         except:
            self.fail(msg='The machine information is wrong in the configuration page')
@@ -131,49 +145,42 @@
 
 #   testcase (1517)
     def test_verify_machine_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
 
         try:
             self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
-            self.assertTrue(re.search("qemux86",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
+            self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
             self.driver.find_element(By.ID, "change-machine-toggle").click()
-            time.sleep(2)
-            self.wait_until_visible('#select-machine-form')
-            self.wait_until_visible('#cancel-machine-change')
+            self.wait_until_visible('#select-machine-form', poll=10)
+            self.wait_until_visible('#cancel-machine-change', poll=10)
             self.driver.find_element(By.ID, "cancel-machine-change").click()
         except:
             self.fail(msg='The machine information is wrong in the configuration page')
 
 #   testcase (1518)
     def test_verify_most_built_recipes_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+        self.wait_until_present('#config-nav', poll=10)
         project_URL=self.get_URL()
-        time.sleep(2)
         try:
             self.assertTrue(re.search("You haven't built any recipes yet",self.driver.find_element(By.ID, "no-most-built").text),'Default message of no builds is not present')
             self.driver.find_element(By.XPATH, "//div[@id='no-most-built']/p/a[@href="+'"'+project_URL+'images/"'+"]").click()
-            time.sleep(2)
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Choose a recipe to build link  is not working  properly')
         except:
             self.fail(msg='No Most built information in project detail page')
 
 #   testcase (1519)
     def test_verify_project_release_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
 
         try:
             self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.ID, "project-release-title").text),'The project release is not defined')
@@ -182,12 +189,11 @@
 
 #   testcase (1520)
     def test_verify_layer_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+        self.wait_until_present('#config-nav', poll=10)
         project_URL=self.get_URL()
-        time.sleep(2)
         try:
            self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
            self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
@@ -213,18 +219,18 @@
 
 #   testcase (1521)
     def test_verify_project_detail_links(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+        self.wait_until_present('#config-nav', poll=10)
         project_URL=self.get_URL()
-        time.sleep(2)
         self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").click()
+        self.wait_until_present('#config-nav', poll=10)
         self.assertTrue(re.search("Configuration",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").text), 'Configuration tab in project topbar is misspelled')
 
         try:
             self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").click()
+            self.wait_until_visible('#project-topbar', poll=10)
             self.assertTrue(re.search("Builds",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").text), 'Builds tab in project topbar is misspelled')
             self.driver.find_element(By.XPATH, "//div[@id='empty-state-projectbuildstable']")
         except:
@@ -232,6 +238,7 @@
 
         try:
             self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").click()
+            self.wait_until_visible('#project-topbar', poll=10)
             self.assertTrue(re.search("Import layer",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").text), 'Import layer tab in project topbar is misspelled')
             self.driver.find_element(By.XPATH, "//fieldset[@id='repo-select']")
             self.driver.find_element(By.XPATH, "//fieldset[@id='git-repo']")
@@ -240,6 +247,7 @@
 
         try:
             self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").click()
+            self.wait_until_visible('#project-topbar', poll=10)
             self.assertTrue(re.search("New custom image",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").text), 'New custom image tab in project topbar is misspelled')
             self.assertTrue(re.search("Select the image recipe you want to customise",self.driver.find_element(By.XPATH, "//div[@class='col-md-12']/h2").text),'The new custom image tab is not loading correctly')
         except:
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_config.py b/poky/bitbake/lib/toaster/tests/functional/test_project_config.py
new file mode 100644
index 0000000..dbee36a
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_config.py
@@ -0,0 +1,341 @@
+#! /usr/bin/env python3 #
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import string
+import random
+import pytest
+from django.urls import reverse
+from selenium.webdriver import Keys
+from selenium.webdriver.support.select import Select
+from selenium.common.exceptions import TimeoutException
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from selenium.webdriver.common.by import By
+
+from .utils import get_projectId_from_url
+
+
+@pytest.mark.django_db
+@pytest.mark.order("last")
+class TestProjectConfig(SeleniumFunctionalTestCase):
+    project_id = None
+    PROJECT_NAME = 'TestProjectConfig'
+    INVALID_PATH_START_TEXT = 'The directory path should either start with a /'
+    INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \
+        'any of these characters'
+
+    def _create_project(self, project_name):
+        """ Create/Test new project using:
+          - Project Name: Any string
+          - Release: Any string
+          - Merge Toaster settings: True or False
+        """
+        self.get(reverse('newproject'))
+        self.wait_until_visible('#new-project-name', poll=2)
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
+        select.select_by_value('3')
+
+        # check merge toaster settings
+        checkbox = self.find('.checkbox-mergeattr')
+        if not checkbox.is_selected():
+            checkbox.click()
+
+        if self.PROJECT_NAME != 'TestProjectConfig':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectConfig'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name', poll=2)
+            url = reverse('project', args=(TestProjectConfig.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _get_config_nav_item(self, index):
+        config_nav = self.find('#config-nav')
+        return config_nav.find_elements(By.TAG_NAME, 'li')[index]
+
+    def _navigate_bbv_page(self):
+        """ Navigate to project BitBake variables page """
+        # check if the menu is displayed
+        if TestProjectConfig.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectConfig.project_id = get_projectId_from_url(current_url)
+        else:
+            url = reverse('projectconf', args=(TestProjectConfig.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav', poll=3)
+        bbv_page_link = self._get_config_nav_item(9)
+        bbv_page_link.click()
+        self.wait_until_visible('#config-nav', poll=3)
+
+    def test_no_underscore_iamgefs_type(self):
+        """
+        Should not accept IMAGEFS_TYPE with an underscore
+        """
+        self._navigate_bbv_page()
+        imagefs_type = "foo_bar"
+
+        self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+        self.click('#change-image_fstypes-icon')
+
+        self.enter_text('#new-imagefs_types', imagefs_type)
+
+        element = self.wait_until_visible('#hintError-image-fs_type', poll=2)
+
+        self.assertTrue(("A valid image type cannot include underscores" in element.text),
+                        "Did not find underscore error message")
+
+    def test_checkbox_verification(self):
+        """
+        Should automatically check the checkbox if user enters value
+        text box, if value is there in the checkbox.
+        """
+        self._navigate_bbv_page()
+
+        imagefs_type = "btrfs"
+
+        self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+        self.click('#change-image_fstypes-icon')
+
+        self.enter_text('#new-imagefs_types', imagefs_type)
+
+        checkboxes = self.driver.find_elements(By.XPATH, "//input[@class='fs-checkbox-fstypes']")
+
+        for checkbox in checkboxes:
+            if checkbox.get_attribute("value") == "btrfs":
+               self.assertEqual(checkbox.is_selected(), True)
+
+    def test_textbox_with_checkbox_verification(self):
+        """
+        Should automatically add or remove value in textbox, if user checks
+        or unchecks checkboxes.
+        """
+        self._navigate_bbv_page()
+
+        self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+        self.click('#change-image_fstypes-icon')
+
+        checkboxes_selector = '.fs-checkbox-fstypes'
+
+        self.wait_until_visible(checkboxes_selector, poll=2)
+        checkboxes = self.find_all(checkboxes_selector)
+
+        for checkbox in checkboxes:
+            if checkbox.get_attribute("value") == "cpio":
+               checkbox.click()
+               element = self.driver.find_element(By.ID, 'new-imagefs_types')
+
+               self.wait_until_visible('#new-imagefs_types', poll=2)
+
+               self.assertTrue(("cpio" in element.get_attribute('value'),
+                               "Imagefs not added into the textbox"))
+               checkbox.click()
+               self.assertTrue(("cpio" not in element.text),
+                               "Image still present in the textbox")
+
+    def test_set_download_dir(self):
+        """
+        Validate the allowed and disallowed types in the directory field for
+        DL_DIR
+        """
+        self._navigate_bbv_page()
+
+        # activate the input to edit download dir
+        try:
+            change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
+        except TimeoutException:
+            # If download dir is not displayed, test is skipped
+            change_dl_dir_btn = None
+
+        if change_dl_dir_btn:
+            change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
+            change_dl_dir_btn.click()
+
+            # downloads dir path doesn't start with / or ${...}
+            input_field = self.wait_until_visible('#new-dl_dir', poll=2)
+            input_field.clear()
+            self.enter_text('#new-dl_dir', 'home/foo')
+            element = self.wait_until_visible('#hintError-initialChar-dl_dir', poll=2)
+
+            msg = 'downloads directory path starts with invalid character but ' \
+                'treated as valid'
+            self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
+
+            # downloads dir path has a space
+            self.driver.find_element(By.ID, 'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '/foo/bar a')
+
+            element = self.wait_until_visible('#hintError-dl_dir', poll=2)
+            msg = 'downloads directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # downloads dir path starts with ${...} but has a space
+            self.driver.find_element(By.ID,'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '${TOPDIR}/down foo')
+
+            element = self.wait_until_visible('#hintError-dl_dir', poll=2)
+            msg = 'downloads directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # downloads dir path starts with /
+            self.driver.find_element(By.ID,'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '/bar/foo')
+
+            hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'downloads directory path valid but treated as invalid')
+
+            # downloads dir path starts with ${...}
+            self.driver.find_element(By.ID,'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '${TOPDIR}/down')
+
+            hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'downloads directory path valid but treated as invalid')
+
+    def test_set_sstate_dir(self):
+        """
+        Validate the allowed and disallowed types in the directory field for
+        SSTATE_DIR
+        """
+        self._navigate_bbv_page()
+
+        try:
+            btn_chg_sstate_dir = self.wait_until_visible(
+                '#change-sstate_dir-icon',
+                poll=2
+            )
+            self.click('#change-sstate_dir-icon')
+        except TimeoutException:
+            # If sstate_dir is not displayed, test is skipped
+            btn_chg_sstate_dir = None
+
+        if btn_chg_sstate_dir:  # Skip continuation if sstate_dir is not displayed
+            # path doesn't start with / or ${...}
+            input_field = self.wait_until_visible('#new-sstate_dir', poll=2)
+            input_field.clear()
+            self.enter_text('#new-sstate_dir', 'home/foo')
+            element = self.wait_until_visible('#hintError-initialChar-sstate_dir', poll=2)
+
+            msg = 'sstate directory path starts with invalid character but ' \
+                'treated as valid'
+            self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
+
+            # path has a space
+            self.driver.find_element(By.ID, 'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '/foo/bar a')
+
+            element = self.wait_until_visible('#hintError-sstate_dir', poll=2)
+            msg = 'sstate directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # path starts with ${...} but has a space
+            self.driver.find_element(By.ID,'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo')
+
+            element = self.wait_until_visible('#hintError-sstate_dir', poll=2)
+            msg = 'sstate directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # path starts with /
+            self.driver.find_element(By.ID,'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '/bar/foo')
+
+            hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'sstate directory path valid but treated as invalid')
+
+            # paths starts with ${...}
+            self.driver.find_element(By.ID, 'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '${TOPDIR}/down')
+
+            hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'sstate directory path valid but treated as invalid')
+
+    def _change_bbv_value(self, **kwargs):
+        var_name, field, btn_id, input_id, value, save_btn, *_ = kwargs.values()
+        """ Change bitbake variable value """
+        self._navigate_bbv_page()
+        self.wait_until_visible(f'#{btn_id}', poll=2)
+        if kwargs.get('new_variable'):
+            self.find(f"#{btn_id}").clear()
+            self.enter_text(f"#{btn_id}", f"{var_name}")
+        else:
+            self.click(f'#{btn_id}')
+            self.wait_until_visible(f'#{input_id}', poll=2)
+
+        if kwargs.get('is_select'):
+            select = Select(self.find(f'#{input_id}'))
+            select.select_by_visible_text(value)
+        else:
+            self.find(f"#{input_id}").clear()
+            self.enter_text(f'#{input_id}', f'{value}')
+        self.click(f'#{save_btn}')
+        value_displayed = str(self.wait_until_visible(f'#{field}').text).lower()
+        msg = f'{var_name} variable not changed'
+        self.assertTrue(str(value).lower() in value_displayed, msg)
+
+    def test_change_distro_var(self):
+        """ Test changing distro variable """
+        self._change_bbv_value(
+            var_name='DISTRO',
+            field='distro',
+            btn_id='change-distro-icon',
+            input_id='new-distro',
+            value='poky-changed',
+            save_btn="apply-change-distro",
+        )
+
+    def test_set_image_install_append_var(self):
+        """ Test setting IMAGE_INSTALL:append variable """
+        self._change_bbv_value(
+            var_name='IMAGE_INSTALL:append',
+            field='image_install',
+            btn_id='change-image_install-icon',
+            input_id='new-image_install',
+            value='bash, apt, busybox',
+            save_btn="apply-change-image_install",
+        )
+
+    def test_set_package_classes_var(self):
+        """ Test setting PACKAGE_CLASSES variable """
+        self._change_bbv_value(
+            var_name='PACKAGE_CLASSES',
+            field='package_classes',
+            btn_id='change-package_classes-icon',
+            input_id='package_classes-select',
+            value='package_deb',
+            save_btn="apply-change-package_classes",
+            is_select=True,
+        )
+
+    def test_create_new_bbv(self):
+        """ Test creating new bitbake variable """
+        self._change_bbv_value(
+            var_name='New_Custom_Variable',
+            field='configvar-list',
+            btn_id='variable',
+            input_id='value',
+            value='new variable value',
+            save_btn="add-configvar-button",
+            new_variable=True
+        )
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_page.py b/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
index 03f64f8..31177cc 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
@@ -6,88 +6,89 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 import random
 import string
+from unittest import skip
 import pytest
-from time import sleep
 from django.urls import reverse
 from django.utils import timezone
 from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.support.select import Select
-from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from selenium.common.exceptions import TimeoutException
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from orm.models import Build, Project, Target
 from selenium.webdriver.common.by import By
 
+from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
+
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestProjectPage(SeleniumFunctionalTestCase):
+    project_id = None
+    PROJECT_NAME = 'TestProjectPage'
 
-    def setUp(self):
-        super().setUp()
-        release = '3'
-        project_name = 'project_' + self.generate_random_string()
-        self._create_test_new_project(
-            project_name,
-            release,
-            False,
-        )
-
-    def generate_random_string(self, length=10):
-        characters = string.ascii_letters + string.digits  # alphabetic and numerical characters
-        random_string = ''.join(random.choice(characters) for _ in range(length))
-        return random_string
-
-    def _create_test_new_project(
-        self,
-        project_name,
-        release,
-        merge_toaster_settings,
-    ):
+    def _create_project(self, project_name):
         """ Create/Test new project using:
           - Project Name: Any string
           - Release: Any string
           - Merge Toaster settings: True or False
         """
         self.get(reverse('newproject'))
-        self.driver.find_element(By.ID,
-                                 "new-project-name").send_keys(project_name)
-
-        select = Select(self.find('#projectversion'))
-        select.select_by_value(release)
+        self.wait_until_visible('#new-project-name')
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
+        select.select_by_value('3')
 
         # check merge toaster settings
         checkbox = self.find('.checkbox-mergeattr')
-        if merge_toaster_settings:
-            if not checkbox.is_selected():
-                checkbox.click()
-        else:
-            if checkbox.is_selected():
-                checkbox.click()
+        if not checkbox.is_selected():
+            checkbox.click()
 
-        self.driver.find_element(By.ID, "create-project-button").click()
+        if self.PROJECT_NAME != 'TestProjectPage':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectPage'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name')
+            url = reverse('project', args=(TestProjectPage.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _navigate_to_project_page(self):
+        # Navigate to project page
+        if TestProjectPage.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectPage.project_id = get_projectId_from_url(current_url)
+        else:
+            url = reverse('project', args=(TestProjectPage.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav')
 
     def _get_create_builds(self, **kwargs):
         """ Create a build and return the build object """
         # parameters for builds to associate with the projects
         now = timezone.now()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name+"2",
-            release,
-            False,
-        )
-
         self.project1_build_success = {
-            'project': Project.objects.get(id=1),
+            'project': Project.objects.get(id=TestProjectPage.project_id),
             'started_on': now,
             'completed_on': now,
             'outcome': Build.SUCCEEDED
         }
 
         self.project1_build_failure = {
-            'project': Project.objects.get(id=1),
+            'project': Project.objects.get(id=TestProjectPage.project_id),
             'started_on': now,
             'completed_on': now,
             'outcome': Build.FAILED
@@ -180,9 +181,7 @@
 
     def _navigate_to_config_nav(self, nav_id, nav_index):
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-        self.wait_until_visible('#config-nav')
+        self._navigate_to_project_page()
         # click on "Software recipe" tab
         soft_recipe = self._get_config_nav_item(nav_index)
         soft_recipe.click()
@@ -211,29 +210,6 @@
                 if row_to_show not in to_skip:
                     test_show_rows(row_to_show, show_row_link)
 
-    def _wait_until_build(self, state):
-        timeout = 10
-        start_time = 0
-        while True:
-            if start_time > timeout:
-                raise TimeoutException(
-                    f'Build did not reach {state} state within {timeout} seconds'
-                )
-            try:
-                last_build_state = self.driver.find_element(
-                    By.XPATH,
-                    '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
-                )
-                build_state = last_build_state.get_attribute(
-                    'data-build-state')
-                state_text = state.lower().split()
-                if any(x in str(build_state).lower() for x in state_text):
-                    break
-            except NoSuchElementException:
-                continue
-            start_time += 1
-            sleep(1) # take a breath and try again
-
     def _mixin_test_table_search_input(self, **kwargs):
         input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values()
         # Test search input
@@ -245,11 +221,19 @@
         rows = self.find_all(f'#{table_selector} tbody tr')
         self.assertTrue(len(rows) > 0)
 
+    def test_create_project(self):
+        """ Create/Test new project using:
+          - Project Name: Any string
+          - Release: Any string
+          - Merge Toaster settings: True or False
+        """
+        self._create_project(project_name=self.PROJECT_NAME)
+
     def test_image_recipe_editColumn(self):
         """ Test the edit column feature in image recipe table on project page """
         self._get_create_builds(success=10, failure=10)
 
-        url = reverse('projectimagerecipes', args=(1,))
+        url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,))
         self.get(url)
         self.wait_until_present('#imagerecipestable tbody tr')
 
@@ -276,8 +260,7 @@
           - AT RIGHT -> button "New project", displayed, clickable
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # check page header
         # AT LEFT -> Logo of Yocto project
@@ -360,8 +343,7 @@
           - Check project name is changed
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # click on "Edit" icon button
         self.wait_until_visible('#project-name-container')
@@ -388,8 +370,7 @@
           Check search box used to build recipes
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # check "configuration" tab
         self.wait_until_visible('#topbar-configuration-tab')
@@ -397,7 +378,7 @@
         self.assertTrue(config_tab.get_attribute('class') == 'active')
         self.assertTrue('Configuration' in str(config_tab.text))
         self.assertTrue(
-            f"/toastergui/project/1" in str(self.driver.current_url)
+            f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url)
         )
 
         def get_tabs():
@@ -420,7 +401,7 @@
         check_tab_link(
             1,
             'Builds',
-            f"/toastergui/project/1/builds"
+            f"/toastergui/project/{TestProjectPage.project_id}/builds"
         )
 
         # check "Import layers" tab
@@ -429,7 +410,7 @@
         check_tab_link(
             2,
             'Import layer',
-            f"/toastergui/project/1/importlayer"
+            f"/toastergui/project/{TestProjectPage.project_id}/importlayer"
         )
 
         # check "New custom image" tab
@@ -438,7 +419,7 @@
         check_tab_link(
             3,
             'New custom image',
-            f"/toastergui/project/1/newcustomimage"
+            f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage"
         )
 
         # check search box can be use to build recipes
@@ -480,12 +461,20 @@
             '//td[@class="add-del-layers"]//a[1]'
         )
         build_btn.click()
-        self._wait_until_build('parsing starting cloning queued')
+        build_state = wait_until_build(self, 'queued cloning starting parsing failed')
         lastest_builds = self.driver.find_elements(
             By.XPATH,
             '//div[@id="latest-builds"]/div'
         )
         self.assertTrue(len(lastest_builds) > 0)
+        last_build = lastest_builds[0]
+        cancel_button = last_build.find_element(
+            By.XPATH,
+            '//span[@class="cancel-build-btn pull-right alert-link"]',
+        )
+        cancel_button.click()
+        if 'starting' not in build_state:  # change build state when cancelled in starting state
+            wait_until_build_cancelled(self)
 
         # check software recipe table feature(show/hide column, pagination)
         self._navigate_to_config_nav('softwarerecipestable', 4)
@@ -505,7 +494,10 @@
         )
         self._navigate_to_config_nav('softwarerecipestable', 4)
         # check show rows(pagination)
-        self._mixin_test_table_show_rows(table_selector='softwarerecipestable')
+        self._mixin_test_table_show_rows(
+            table_selector='softwarerecipestable',
+            to_skip=[150],
+        )
 
     def test_machines_page(self):
         """ Test Machine page
@@ -547,6 +539,7 @@
             searchBtn_selector='search-submit-machinestable',
             table_selector='machinestable'
         )
+        self.wait_until_visible('#machinestable tbody tr', poll=3)
         rows = self.find_all('#machinestable tbody tr')
         machine_to_add = rows[0]
         add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]')
@@ -571,7 +564,10 @@
         )
         self._navigate_to_config_nav('machinestable', 5)
         # check show rows(pagination)
-        self._mixin_test_table_show_rows(table_selector='machinestable')
+        self._mixin_test_table_show_rows(
+            table_selector='machinestable',
+            to_skip=[150],
+        )
 
     def test_layers_page(self):
         """ Test layers page
@@ -593,6 +589,7 @@
             table_selector='layerstable'
         )
         # check "Add layer" button works
+        self.wait_until_visible('#layerstable tbody tr', poll=3)
         rows = self.find_all('#layerstable tbody tr')
         layer_to_add = rows[0]
         add_btn = layer_to_add.find_element(
@@ -601,7 +598,7 @@
         )
         add_btn.click()
         # check modal is displayed
-        self.wait_until_visible('#dependencies-modal', poll=2)
+        self.wait_until_visible('#dependencies-modal', poll=3)
         list_dependencies = self.find_all('#dependencies-list li')
         # click on add-layers button
         add_layers_btn = self.driver.find_element(
@@ -615,6 +612,7 @@
             f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text)
         )
         # check "Remove layer" button works
+        self.wait_until_visible('#layerstable tbody tr', poll=3)
         rows = self.find_all('#layerstable tbody tr')
         layer_to_remove = rows[0]
         remove_btn = layer_to_remove.find_element(
@@ -643,7 +641,10 @@
         )
         self._navigate_to_config_nav('layerstable', 6)
         # check show rows(pagination)
-        self._mixin_test_table_show_rows(table_selector='layerstable')
+        self._mixin_test_table_show_rows(
+            table_selector='layerstable',
+            to_skip=[150],
+        )
 
     def test_distro_page(self):
         """ Test distros page
@@ -693,7 +694,7 @@
         # check show rows(pagination)
         self._mixin_test_table_show_rows(
             table_selector='distrostable',
-            to_skip=[150]
+            to_skip=[150],
         )
 
     def test_single_layer_page(self):
@@ -706,7 +707,7 @@
                 - Check layer summary
                 - Check layer description
         """
-        url = reverse("layerdetails", args=(1, 8))
+        url = reverse("layerdetails", args=(TestProjectPage.project_id, 8))
         self.get(url)
         self.wait_until_visible('.page-header')
         # check title is displayed
@@ -765,7 +766,7 @@
                 - Check recipe: name, summary, description, Version, Section,
                 License, Approx. packages included, Approx. size, Recipe file
         """
-        url = reverse("recipedetails", args=(1, 53428))
+        url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428))
         self.get(url)
         self.wait_until_visible('.page-header')
         # check title is displayed
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py b/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
index 23012d7..ee1f5c4 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
@@ -6,102 +6,102 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
-from time import sleep
+import string
+import random
 import pytest
-from django.utils import timezone
 from django.urls import reverse
 from selenium.webdriver import Keys
 from selenium.webdriver.support.select import Select
-from selenium.common.exceptions import NoSuchElementException
-from orm.models import Build, Project, Target
+from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from orm.models import Project
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from selenium.webdriver.common.by import By
 
+from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
+
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestProjectConfigTab(SeleniumFunctionalTestCase):
+    PROJECT_NAME = 'TestProjectConfigTab'
+    project_id = None
 
-    def setUp(self):
-        self.recipe = None
-        super().setUp()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name,
-            release,
-            False,
-        )
-
-    def _create_test_new_project(
-        self,
-        project_name,
-        release,
-        merge_toaster_settings,
-    ):
+    def _create_project(self, project_name, **kwargs):
         """ Create/Test new project using:
           - Project Name: Any string
           - Release: Any string
           - Merge Toaster settings: True or False
         """
+        release = kwargs.get('release', '3')
         self.get(reverse('newproject'))
-        self.driver.find_element(By.ID,
-                                 "new-project-name").send_keys(project_name)
-
-        select = Select(self.find('#projectversion'))
+        self.wait_until_visible('#new-project-name')
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
         select.select_by_value(release)
 
         # check merge toaster settings
         checkbox = self.find('.checkbox-mergeattr')
-        if merge_toaster_settings:
-            if not checkbox.is_selected():
-                checkbox.click()
+        if not checkbox.is_selected():
+            checkbox.click()
+
+        if self.PROJECT_NAME != 'TestProjectConfigTab':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectConfigTab'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name', poll=3)
+            url = reverse('project', args=(TestProjectConfigTab.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _navigate_to_project_page(self):
+        # Navigate to project page
+        if TestProjectConfigTab.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectConfigTab.project_id = get_projectId_from_url(
+                current_url)
         else:
-            if checkbox.is_selected():
-                checkbox.click()
-
-        self.driver.find_element(By.ID, "create-project-button").click()
-
-    @classmethod
-    def _wait_until_build(cls, state):
-        while True:
-            try:
-                last_build_state = cls.driver.find_element(
-                    By.XPATH,
-                    '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
-                )
-                build_state = last_build_state.get_attribute(
-                    'data-build-state')
-                state_text = state.lower().split()
-                if any(x in str(build_state).lower() for x in state_text):
-                    break
-            except NoSuchElementException:
-                continue
-            sleep(1)
+            url = reverse('project', args=(TestProjectConfigTab.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav')
 
     def _create_builds(self):
         # check search box can be use to build recipes
         search_box = self.find('#build-input')
-        search_box.send_keys('core-image-minimal')
+        search_box.send_keys('foo')
         self.find('#build-button').click()
-        sleep(1)
-        self.wait_until_visible('#latest-builds')
+        self.wait_until_present('#latest-builds')
         # loop until reach the parsing state
-        self._wait_until_build('parsing starting cloning')
+        wait_until_build(self, 'queued cloning starting parsing failed')
         lastest_builds = self.driver.find_elements(
             By.XPATH,
             '//div[@id="latest-builds"]/div',
         )
         last_build = lastest_builds[0]
         self.assertTrue(
-            'core-image-minimal' in str(last_build.text)
+            'foo' in str(last_build.text)
         )
-        cancel_button = last_build.find_element(
-            By.XPATH,
-            '//span[@class="cancel-build-btn pull-right alert-link"]',
-        )
-        cancel_button.click()
-        sleep(1)
-        self._wait_until_build('cancelled')
+        last_build = lastest_builds[0]
+        try:
+            cancel_button = last_build.find_element(
+                By.XPATH,
+                '//span[@class="cancel-build-btn pull-right alert-link"]',
+            )
+            cancel_button.click()
+        except NoSuchElementException:
+            # Skip if the build is already cancelled
+            pass
+        wait_until_build_cancelled(self)
 
     def _get_tabs(self):
         # tabs links list
@@ -114,64 +114,6 @@
         config_nav = self.find('#config-nav')
         return config_nav.find_elements(By.TAG_NAME, 'li')[index]
 
-    def _get_create_builds(self, **kwargs):
-        """ Create a build and return the build object """
-        # parameters for builds to associate with the projects
-        now = timezone.now()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name+"2",
-            release,
-            False,
-        )
-
-        self.project1_build_success = {
-            'project': Project.objects.get(id=1),
-            'started_on': now,
-            'completed_on': now,
-            'outcome': Build.SUCCEEDED
-        }
-
-        self.project1_build_failure = {
-            'project': Project.objects.get(id=1),
-            'started_on': now,
-            'completed_on': now,
-            'outcome': Build.FAILED
-        }
-        build1 = Build.objects.create(**self.project1_build_success)
-        build2 = Build.objects.create(**self.project1_build_failure)
-
-        # add some targets to these builds so they have recipe links
-        # (and so we can find the row in the ToasterTable corresponding to
-        # a particular build)
-        Target.objects.create(build=build1, target='foo')
-        Target.objects.create(build=build2, target='bar')
-
-        if kwargs:
-            # Create kwargs.get('success') builds with success status with target
-            # and kwargs.get('failure') builds with failure status with target
-            for i in range(kwargs.get('success', 0)):
-                now = timezone.now()
-                self.project1_build_success['started_on'] = now
-                self.project1_build_success[
-                    'completed_on'] = now - timezone.timedelta(days=i)
-                build = Build.objects.create(**self.project1_build_success)
-                Target.objects.create(build=build,
-                                      target=f'{i}_success_recipe',
-                                      task=f'{i}_success_task')
-
-            for i in range(kwargs.get('failure', 0)):
-                now = timezone.now()
-                self.project1_build_failure['started_on'] = now
-                self.project1_build_failure[
-                    'completed_on'] = now - timezone.timedelta(days=i)
-                build = Build.objects.create(**self.project1_build_failure)
-                Target.objects.create(build=build,
-                                      target=f'{i}_fail_recipe',
-                                      task=f'{i}_fail_task')
-        return build1, build2
-
     def test_project_config_nav(self):
         """ Test project config tab navigation:
         - Check if the menu is displayed and contains the right elements:
@@ -188,12 +130,7 @@
             - Actions
             - Delete project
         """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
-        # check if the menu is displayed
-        self.wait_until_visible('#config-nav')
+        self._navigate_to_project_page()
 
         def _get_config_nav_item(index):
             config_nav = self.find('#config-nav')
@@ -221,14 +158,28 @@
         self.assertTrue("actions" in str(actions.text).lower())
 
         conf_nav_list = [
-            [0, 'Configuration', f"/toastergui/project/1"],  # config
-            [2, 'Custom images', f"/toastergui/project/1/customimages"],  # custom images
-            [3, 'Image recipes', f"/toastergui/project/1/images"],  # image recipes
-            [4, 'Software recipes', f"/toastergui/project/1/softwarerecipes"],  # software recipes
-            [5, 'Machines', f"/toastergui/project/1/machines"],  # machines
-            [6, 'Layers', f"/toastergui/project/1/layers"],  # layers
-            [7, 'Distro', f"/toastergui/project/1/distro"],  # distro
-            [9, 'BitBake variables', f"/toastergui/project/1/configuration"],  # bitbake variables
+            # config
+            [0, 'Configuration',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}"],
+            # custom images
+            [2, 'Custom images',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"],
+            # image recipes
+            [3, 'Image recipes',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/images"],
+            # software recipes
+            [4, 'Software recipes',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"],
+            # machines
+            [5, 'Machines',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"],
+            # layers
+            [6, 'Layers',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"],
+            # distro
+            [7, 'Distros',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"],
+            #  [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"],  # bitbake variables
         ]
         for index, item_name, url in conf_nav_list:
             item = _get_config_nav_item(index)
@@ -236,6 +187,96 @@
                 item.click()
             check_config_nav_item(index, item_name, url)
 
+    def test_image_recipe_editColumn(self):
+        """ Test the edit column feature in image recipe table on project page """
+        def test_edit_column(check_box_id):
+            # Check that we can hide/show table column
+            check_box = self.find(f'#{check_box_id}')
+            th_class = str(check_box_id).replace('checkbox-', '')
+            if check_box.is_selected():
+                # check if column is visible in table
+                self.assertTrue(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+                )
+                check_box.click()
+                # check if column is hidden in table
+                self.assertFalse(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+                )
+            else:
+                # check if column is hidden in table
+                self.assertFalse(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+                )
+                check_box.click()
+                # check if column is visible in table
+                self.assertTrue(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+                )
+
+        self._navigate_to_project_page()
+        # navigate to project image recipe page
+        recipe_image_page_link = self._get_config_nav_item(3)
+        recipe_image_page_link.click()
+        self.wait_until_present('#imagerecipestable tbody tr')
+
+        # Check edit column
+        edit_column = self.find('#edit-columns-button')
+        self.assertTrue(edit_column.is_displayed())
+        edit_column.click()
+        # Check dropdown is visible
+        self.wait_until_visible('ul.dropdown-menu.editcol')
+
+        # Check that we can hide the edit column
+        test_edit_column('checkbox-get_description_or_summary')
+        test_edit_column('checkbox-layer_version__get_vcs_reference')
+        test_edit_column('checkbox-layer_version__layer__name')
+        test_edit_column('checkbox-license')
+        test_edit_column('checkbox-recipe-file')
+        test_edit_column('checkbox-section')
+        test_edit_column('checkbox-version')
+
+    def test_image_recipe_show_rows(self):
+        """ Test the show rows feature in image recipe table on project page """
+        def test_show_rows(row_to_show, show_row_link):
+            # Check that we can show rows == row_to_show
+            show_row_link.select_by_value(str(row_to_show))
+            self.wait_until_visible('#imagerecipestable tbody tr')
+            self.assertTrue(
+                len(self.find_all('#imagerecipestable tbody tr')) == row_to_show
+            )
+
+        self._navigate_to_project_page()
+        # navigate to project image recipe page
+        recipe_image_page_link = self._get_config_nav_item(3)
+        recipe_image_page_link.click()
+        self.wait_until_present('#imagerecipestable tbody tr')
+
+        show_rows = self.driver.find_elements(
+            By.XPATH,
+            '//select[@class="form-control pagesize-imagerecipestable"]'
+        )
+        # Check show rows
+        for show_row_link in show_rows:
+            show_row_link = Select(show_row_link)
+            test_show_rows(10, show_row_link)
+            test_show_rows(25, show_row_link)
+            test_show_rows(50, show_row_link)
+            test_show_rows(100, show_row_link)
+            test_show_rows(150, show_row_link)
+
     def test_project_config_tab_right_section(self):
         """ Test project config tab right section contains five blocks:
             - Machine:
@@ -257,35 +298,31 @@
                     - meta-poky
                     - meta-yocto-bsp
         """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
+        # Create a new project for this test
+        project_name = self._random_string(10)
+        self._create_project(project_name=project_name)
         # check if the menu is displayed
         self.wait_until_visible('#project-page')
         block_l = self.driver.find_element(
             By.XPATH, '//*[@id="project-page"]/div[2]')
-        machine = self.find('#machine-section')
-        distro = self.find('#distro-section')
-        most_built_recipes = self.driver.find_element(
-            By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
         project_release = self.driver.find_element(
             By.XPATH, '//*[@id="project-page"]/div[1]/div[4]')
         layers = block_l.find_element(By.ID, 'layer-container')
 
-        def check_machine_distro(self, item_name, new_item_name, block):
+        def check_machine_distro(self, item_name, new_item_name, block_id):
+            block = self.find(f'#{block_id}')
             title = block.find_element(By.TAG_NAME, 'h3')
             self.assertTrue(item_name.capitalize() in title.text)
-            edit_btn = block.find_element(By.ID, f'change-{item_name}-toggle')
+            edit_btn = self.find(f'#change-{item_name}-toggle')
             edit_btn.click()
-            sleep(1)
-            name_input = block.find_element(By.ID, f'{item_name}-change-input')
+            self.wait_until_visible(f'#{item_name}-change-input')
+            name_input = self.find(f'#{item_name}-change-input')
             name_input.clear()
             name_input.send_keys(new_item_name)
-            change_btn = block.find_element(By.ID, f'{item_name}-change-btn')
+            change_btn = self.find(f'#{item_name}-change-btn')
             change_btn.click()
-            sleep(1)
-            project_name = block.find_element(By.ID, f'project-{item_name}-name')
+            self.wait_until_visible(f'#project-{item_name}-name')
+            project_name = self.find(f'#project-{item_name}-name')
             self.assertTrue(new_item_name in project_name.text)
             # check change notificaiton is displayed
             change_notification = self.find('#change-notification')
@@ -294,9 +331,9 @@
             )
 
         # Machine
-        check_machine_distro(self, 'machine', 'qemux86-64', machine)
+        check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section')
         # Distro
-        check_machine_distro(self, 'distro', 'poky-altcfg', distro)
+        check_machine_distro(self, 'distro', 'poky-altcfg', 'distro-section')
 
         # Project release
         title = project_release.find_element(By.TAG_NAME, 'h3')
@@ -304,7 +341,6 @@
         self.assertTrue(
             "Yocto Project master" in self.find('#project-release-title').text
         )
-
         # Layers
         title = layers.find_element(By.TAG_NAME, 'h3')
         self.assertTrue("Layers" in title.text)
@@ -314,7 +350,9 @@
         # meta-yocto-bsp
         layers_list = layers.find_element(By.ID, 'layers-in-project-list')
         layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
-        self.assertTrue(len(layers_list_items) == 3)
+        # remove all layers except the first three layers
+        for i in range(3, len(layers_list_items)):
+            layers_list_items[i].find_element(By.TAG_NAME, 'span').click()
         # check can add a layer if exists
         add_layer_input = layers.find_element(By.ID, 'layer-add-input')
         add_layer_input.send_keys('meta-oe')
@@ -326,56 +364,70 @@
         dropdown_item.click()
         add_layer_btn = layers.find_element(By.ID, 'add-layer-btn')
         add_layer_btn.click()
-        sleep(1)
+        self.wait_until_visible('#layers-in-project-list')
         # check layer is added
         layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
         self.assertTrue(len(layers_list_items) == 4)
 
-        # Most built recipes
-        title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
-        self.assertTrue("Most built recipes" in title.text)
-        # Create a new builds 5
+    def test_most_build_recipes(self):
+        """ Test most build recipes block contains"""
+        def rebuild_from_most_build_recipes(recipe_list_items):
+            checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
+            checkbox.click()
+            build_btn = self.find('#freq-build-btn')
+            build_btn.click()
+            self.wait_until_visible('#latest-builds')
+            wait_until_build(self, 'queued cloning starting parsing failed')
+            lastest_builds = self.driver.find_elements(
+                By.XPATH,
+                '//div[@id="latest-builds"]/div'
+            )
+            self.assertTrue(len(lastest_builds) >= 2)
+            last_build = lastest_builds[0]
+            try:
+                cancel_button = last_build.find_element(
+                    By.XPATH,
+                    '//span[@class="cancel-build-btn pull-right alert-link"]',
+                )
+                cancel_button.click()
+            except NoSuchElementException:
+                # Skip if the build is already cancelled
+                pass
+            wait_until_build_cancelled(self)
+        # Create a new project for remaining asserts
+        project_name = self._random_string(10)
+        self._create_project(project_name=project_name, release='2')
+        current_url = self.driver.current_url
+        TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
+        url = current_url.split('?')[0]
+
+        # Create a new builds
         self._create_builds()
 
-        # Refresh the page
-        self.get(url)
+        # back to project page
+        self.driver.get(url)
 
-        sleep(1)  # wait for page to load
-        self.wait_until_visible('#project-page')
-        # check can select a recipe and build it
+        self.wait_until_visible('#project-page', poll=3)
+
+        # Most built recipes
         most_built_recipes = self.driver.find_element(
             By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
-        recipe_list = most_built_recipes.find_element(By.ID, 'freq-build-list')
+        title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
+        self.assertTrue("Most built recipes" in title.text)
+        # check can select a recipe and build it
+        self.wait_until_visible('#freq-build-list', poll=3)
+        recipe_list = self.find('#freq-build-list')
         recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li')
         self.assertTrue(
             len(recipe_list_items) > 0,
-            msg="No recipes found in the most built recipes list",
+            msg="Any recipes found in the most built recipes list",
         )
-        checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
-        checkbox.click()
-        build_btn = self.find('#freq-build-btn')
-        build_btn.click()
-        sleep(1)  # wait for page to load
-        self.wait_until_visible('#latest-builds')
-        self._wait_until_build('parsing starting cloning queueing')
-        lastest_builds = self.driver.find_elements(
-            By.XPATH,
-            '//div[@id="latest-builds"]/div'
-        )
-        last_build = lastest_builds[0]
-        cancel_button = last_build.find_element(
-            By.XPATH,
-            '//span[@class="cancel-build-btn pull-right alert-link"]',
-        )
-        cancel_button.click()
-        self.assertTrue(len(lastest_builds) == 2)
+        rebuild_from_most_build_recipes(recipe_list_items)
+        TestProjectConfigTab.project_id = None  # reset project id
 
     def test_project_page_tab_importlayer(self):
         """ Test project page tab import layer """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
+        self._navigate_to_project_page()
         # navigate to "Import layers" tab
         import_layers_tab = self._get_tabs()[2]
         import_layers_tab.find_element(By.TAG_NAME, 'a').click()
@@ -415,10 +467,10 @@
 
     def test_project_page_custom_image_no_image(self):
         """ Test project page tab "New custom image" when no custom image """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
+        project_name = self._random_string(10)
+        self._create_project(project_name=project_name)
+        current_url = self.driver.current_url
+        TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
         # navigate to "Custom image" tab
         custom_image_section = self._get_config_nav_item(2)
         custom_image_section.click()
@@ -433,8 +485,9 @@
         div_empty_msg = self.find('#empty-state-customimagestable')
         link_create_custom_image = div_empty_msg.find_element(
             By.TAG_NAME, 'a')
+        self.assertTrue(TestProjectConfigTab.project_id is not None)
         self.assertTrue(
-            f"/toastergui/project/1/newcustomimage" in str(
+            f"/toastergui/project/{TestProjectConfigTab.project_id}/newcustomimage" in str(
                 link_create_custom_image.get_attribute('href')
             )
         )
@@ -443,6 +496,7 @@
                 link_create_custom_image.text
             )
         )
+        TestProjectConfigTab.project_id = None  # reset project id
 
     def test_project_page_image_recipe(self):
         """ Test project page section images
@@ -451,11 +505,7 @@
             - Check image recipe build button works
             - Check image recipe table features(show/hide column, pagination)
         """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-        self.wait_until_visible('#config-nav')
-
+        self._navigate_to_project_page()
         # navigate to "Images section"
         images_section = self._get_config_nav_item(3)
         images_section.click()
@@ -471,108 +521,3 @@
         self.wait_until_visible('#imagerecipestable tbody tr')
         rows = self.find_all('#imagerecipestable tbody tr')
         self.assertTrue(len(rows) > 0)
-
-        # Test build button
-        image_to_build = rows[0]
-        build_btn = image_to_build.find_element(
-            By.XPATH,
-            '//td[@class="add-del-layers"]'
-        )
-        build_btn.click()
-        self._wait_until_build('parsing starting cloning')
-        lastest_builds = self.driver.find_elements(
-            By.XPATH,
-            '//div[@id="latest-builds"]/div'
-        )
-        self.assertTrue(len(lastest_builds) > 0)
-
-    def test_image_recipe_editColumn(self):
-        """ Test the edit column feature in image recipe table on project page """
-        self._get_create_builds(success=10, failure=10)
-
-        def test_edit_column(check_box_id):
-            # Check that we can hide/show table column
-            check_box = self.find(f'#{check_box_id}')
-            th_class = str(check_box_id).replace('checkbox-', '')
-            if check_box.is_selected():
-                # check if column is visible in table
-                self.assertTrue(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
-                )
-                check_box.click()
-                # check if column is hidden in table
-                self.assertFalse(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
-                )
-            else:
-                # check if column is hidden in table
-                self.assertFalse(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
-                )
-                check_box.click()
-                # check if column is visible in table
-                self.assertTrue(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
-                )
-
-        url = reverse('projectimagerecipes', args=(1,))
-        self.get(url)
-        self.wait_until_present('#imagerecipestable tbody tr')
-
-        # Check edit column
-        edit_column = self.find('#edit-columns-button')
-        self.assertTrue(edit_column.is_displayed())
-        edit_column.click()
-        # Check dropdown is visible
-        self.wait_until_visible('ul.dropdown-menu.editcol')
-
-        # Check that we can hide the edit column
-        test_edit_column('checkbox-get_description_or_summary')
-        test_edit_column('checkbox-layer_version__get_vcs_reference')
-        test_edit_column('checkbox-layer_version__layer__name')
-        test_edit_column('checkbox-license')
-        test_edit_column('checkbox-recipe-file')
-        test_edit_column('checkbox-section')
-        test_edit_column('checkbox-version')
-
-    def test_image_recipe_show_rows(self):
-        """ Test the show rows feature in image recipe table on project page """
-        self._get_create_builds(success=100, failure=100)
-
-        def test_show_rows(row_to_show, show_row_link):
-            # Check that we can show rows == row_to_show
-            show_row_link.select_by_value(str(row_to_show))
-            self.wait_until_present('#imagerecipestable tbody tr')
-            sleep(1)
-            self.assertTrue(
-                len(self.find_all('#imagerecipestable tbody tr')) == row_to_show
-            )
-
-        url = reverse('projectimagerecipes', args=(2,))
-        self.get(url)
-        self.wait_until_present('#imagerecipestable tbody tr')
-
-        show_rows = self.driver.find_elements(
-            By.XPATH,
-            '//select[@class="form-control pagesize-imagerecipestable"]'
-        )
-        # Check show rows
-        for show_row_link in show_rows:
-            show_row_link = Select(show_row_link)
-            test_show_rows(10, show_row_link)
-            test_show_rows(25, show_row_link)
-            test_show_rows(50, show_row_link)
-            test_show_rows(100, show_row_link)
-            test_show_rows(150, show_row_link)
diff --git a/poky/bitbake/lib/toaster/tests/functional/utils.py b/poky/bitbake/lib/toaster/tests/functional/utils.py
new file mode 100644
index 0000000..7269fa1
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/utils.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+
+from time import sleep
+from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException
+from selenium.webdriver.common.by import By
+
+from orm.models import Build
+
+
+def wait_until_build(test_instance, state):
+    timeout = 60
+    start_time = 0
+    build_state = ''
+    while True:
+        try:
+            if start_time > timeout:
+                raise TimeoutException(
+                    f'Build did not reach {state} state within {timeout} seconds'
+                )
+            last_build_state = test_instance.driver.find_element(
+                By.XPATH,
+                '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
+            )
+            build_state = last_build_state.get_attribute(
+                'data-build-state')
+            state_text = state.lower().split()
+            if any(x in str(build_state).lower() for x in state_text):
+                return str(build_state).lower()
+            if 'failed' in str(build_state).lower():
+                break
+        except NoSuchElementException:
+            continue
+        except TimeoutException:
+            break
+        start_time += 1
+        sleep(1) # take a breath and try again
+
+def wait_until_build_cancelled(test_instance):
+    """ Cancel build take a while sometime, the method is to wait driver action
+        until build being cancelled
+    """
+    timeout = 30
+    start_time = 0
+    build = None
+    while True:
+        try:
+            if start_time > timeout:
+                raise TimeoutException(
+                    f'Build did not reach cancelled state within {timeout} seconds'
+                )
+            last_build_state = test_instance.driver.find_element(
+                By.XPATH,
+                '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
+            )
+            build_state = last_build_state.get_attribute(
+                'data-build-state')
+            if 'failed' in str(build_state).lower():
+                break
+            if 'cancelling' in str(build_state).lower():
+                # Change build state to cancelled
+                if not build:  # get build object only once
+                    build = Build.objects.last()
+                    build.outcome = Build.CANCELLED
+                    build.save()
+            if 'cancelled' in str(build_state).lower():
+                break
+        except NoSuchElementException:
+            continue
+        except StaleElementReferenceException:
+            continue
+        except TimeoutException:
+            break
+        start_time += 1
+        sleep(1) # take a breath and try again
+
+def get_projectId_from_url(url):
+    # url = 'http://domainename.com/toastergui/project/1656/whatever
+    # or url = 'http://domainename.com/toastergui/project/1/
+    # or url = 'http://domainename.com/toastergui/project/186
+    assert '/toastergui/project/' in url, "URL is not valid"
+    url_to_list = url.split('/toastergui/project/')
+    return  int(url_to_list[1].split('/')[0])  # project_id