Keep aria-valuenow/min/max on role="progressbar" updated live. When the exact percentage is unknowable, omit aria-valuenow entirely and show a continuously moving "indeterminate" stripe instead — at that point it functionally overlaps with a spinner.
The line between the two is whether you know how long it'll take. A determinate task like a file upload, where remaining bytes are known, is better served by a progress bar's extra information; a task with no knowable end, like waiting on a server response, is more honest as a spinner. The difference from a skeleton is that a skeleton fakes the shape of upcoming content to speed up perceived load, while a progress bar reports the numeric progress itself.
Rather than vanishing the instant it hits 100%, holding briefly on a completed state gives more confidence that the task actually finished.